Repository: modin-project/modin

Issue:
BUG: TypeError: cannot pickle 'module' object
### Modin version checks

- [X] I have checked that this issue has not already been reported.

- [X] I have confirmed this bug exists on the latest released version of Modin.

- [X] I have confirmed this bug exists on the main branch of Modin. (In order to do this you can follow [this guide](https://modin.readthedocs.io/en/stable/getting_started/installation.html#installing-from-the-github-master-branch).)


### Reproducible Example

```python
import os
import lazy_import
import ray
pandas = lazy_import.lazy_module("pandas")
pyarrow = lazy_import.lazy_module("pyarrow")
from modin import pandas as pd

os.environ["MODIN_ENGINE"] = "ray"
if not ray.is_initialized():
    ray.init(_plasma_directory="/tmp")

df = pd.DataFrame({"col1": [1, 2, 3], "col2": list("abc")})
```


### Issue Description

Failed to create dataframe if using lazy_import in the file

### Expected Behavior

Should be able to create dataframe normally

### Error Logs

<details>

```python-traceback

    return cls.get_factory()._from_non_pandas(*args, **kwargs)
  File "/Users/kevin/opt/anaconda3/envs/flytekit-3.9/lib/python3.9/site-packages/modin/core/execution/dispatching/factories/dispatcher.py", line 113, in get_factory
    Engine.subscribe(cls._update_factory)
  File "/Users/kevin/opt/anaconda3/envs/flytekit-3.9/lib/python3.9/site-packages/modin/config/pubsub.py", line 217, in subscribe
    callback(cls)
  File "/Users/kevin/opt/anaconda3/envs/flytekit-3.9/lib/python3.9/site-packages/modin/core/execution/dispatching/factories/dispatcher.py", line 155, in _update_factory
    cls.__factory.prepare()
  File "/Users/kevin/opt/anaconda3/envs/flytekit-3.9/lib/python3.9/site-packages/modin/core/execution/dispatching/factories/factories.py", line 441, in prepare
    from modin.core.execution.ray.implementations.pandas_on_ray.io import (
  File "/Users/kevin/opt/anaconda3/envs/flytekit-3.9/lib/python3.9/site-packages/modin/core/execution/ray/implementations/pandas_on_ray/io/__init__.py", line 16, in <module>
    from .io import PandasOnRayIO
  File "/Users/kevin/opt/anaconda3/envs/flytekit-3.9/lib/python3.9/site-packages/modin/core/execution/ray/implementations/pandas_on_ray/io/io.py", line 43, in <module>
    from ..dataframe import PandasOnRayDataframe
  File "/Users/kevin/opt/anaconda3/envs/flytekit-3.9/lib/python3.9/site-packages/modin/core/execution/ray/implementations/pandas_on_ray/dataframe/__init__.py", line 16, in <module>
    from .dataframe import PandasOnRayDataframe
  File "/Users/kevin/opt/anaconda3/envs/flytekit-3.9/lib/python3.9/site-packages/modin/core/execution/ray/implementations/pandas_on_ray/dataframe/dataframe.py", line 16, in <module>
    from ..partitioning.partition_manager import PandasOnRayDataframePartitionManager
  File "/Users/kevin/opt/anaconda3/envs/flytekit-3.9/lib/python3.9/site-packages/modin/core/execution/ray/implementations/pandas_on_ray/partitioning/__init__.py", line 17, in <module>
    from .partition_manager import PandasOnRayDataframePartitionManager
  File "/Users/kevin/opt/anaconda3/envs/flytekit-3.9/lib/python3.9/site-packages/modin/core/execution/ray/implementations/pandas_on_ray/partitioning/partition_manager.py", line 25, in <module>
    from .virtual_partition import (
  File "/Users/kevin/opt/anaconda3/envs/flytekit-3.9/lib/python3.9/site-packages/modin/core/execution/ray/implementations/pandas_on_ray/partitioning/virtual_partition.py", line 28, in <module>
    _DEPLOY_AXIS_FUNC = ray.put(PandasDataframeAxisPartition.deploy_axis_func)
  File "/Users/kevin/opt/anaconda3/envs/flytekit-3.9/lib/python3.9/site-packages/ray/_private/client_mode_hook.py", line 105, in wrapper
    return func(*args, **kwargs)
  File "/Users/kevin/opt/anaconda3/envs/flytekit-3.9/lib/python3.9/site-packages/ray/_private/worker.py", line 2375, in put
    object_ref = worker.put_object(value, owner_address=serialize_owner_address)
  File "/Users/kevin/opt/anaconda3/envs/flytekit-3.9/lib/python3.9/site-packages/ray/_private/worker.py", line 611, in put_object
    serialized_value = self.get_serialization_context().serialize(value)
  File "/Users/kevin/opt/anaconda3/envs/flytekit-3.9/lib/python3.9/site-packages/ray/_private/serialization.py", line 450, in serialize
    return self._serialize_to_msgpack(value)
  File "/Users/kevin/opt/anaconda3/envs/flytekit-3.9/lib/python3.9/site-packages/ray/_private/serialization.py", line 428, in _serialize_to_msgpack
    pickle5_serialized_object = self._serialize_to_pickle5(
  File "/Users/kevin/opt/anaconda3/envs/flytekit-3.9/lib/python3.9/site-packages/ray/_private/serialization.py", line 390, in _serialize_to_pickle5
    raise e
  File "/Users/kevin/opt/anaconda3/envs/flytekit-3.9/lib/python3.9/site-packages/ray/_private/serialization.py", line 385, in _serialize_to_pickle5
    inband = pickle.dumps(
  File "/Users/kevin/opt/anaconda3/envs/flytekit-3.9/lib/python3.9/site-packages/ray/cloudpickle/cloudpickle_fast.py", line 73, in dumps
    cp.dump(obj)
  File "/Users/kevin/opt/anaconda3/envs/flytekit-3.9/lib/python3.9/site-packages/ray/cloudpickle/cloudpickle_fast.py", line 627, in dump
    return Pickler.dump(self, obj)
TypeError: cannot pickle 'module' object
```

</details>


### Installed Versions

<details>

(flytekit-3.9) ➜  ~ pip list | grep modin
modin                          0.18.1
</details>



Discussion:
@pingsutw thanks for the report! I was able to reproduce the issue.

Resolving patch:
diff --git a/modin/core/execution/ray/implementations/pandas_on_ray/partitioning/virtual_partition.py b/modin/core/execution/ray/implementations/pandas_on_ray/partitioning/virtual_partition.py
--- a/modin/core/execution/ray/implementations/pandas_on_ray/partitioning/virtual_partition.py
+++ b/modin/core/execution/ray/implementations/pandas_on_ray/partitioning/virtual_partition.py
@@ -26,13 +26,6 @@
 from modin.utils import _inherit_docstrings
 
 
-# If Ray has not been initialized yet by Modin,
-# it will be initialized when calling `RayWrapper.put`.
-_DEPLOY_AXIS_FUNC = RayWrapper.put(PandasDataframeAxisPartition.deploy_axis_func)
-_DEPLOY_SPLIT_FUNC = RayWrapper.put(PandasDataframeAxisPartition.deploy_splitting_func)
-_DRAIN = RayWrapper.put(PandasDataframeAxisPartition.drain)
-
-
 class PandasOnRayDataframeVirtualPartition(PandasDataframeAxisPartition):
     """
     The class implements the interface in ``PandasDataframeAxisPartition``.
@@ -60,6 +53,33 @@ class PandasOnRayDataframeVirtualPartition(PandasDataframeAxisPartition):
     instance_type = ray.ObjectRef
     axis = None
 
+    # these variables are intentionally initialized at runtime (see #6023)
+    _DEPLOY_AXIS_FUNC = None
+    _DEPLOY_SPLIT_FUNC = None
+    _DRAIN_FUNC = None
+
+    @classmethod
+    def _get_deploy_axis_func(cls):  # noqa: GL08
+        if cls._DEPLOY_AXIS_FUNC is None:
+            cls._DEPLOY_AXIS_FUNC = RayWrapper.put(
+                PandasDataframeAxisPartition.deploy_axis_func
+            )
+        return cls._DEPLOY_AXIS_FUNC
+
+    @classmethod
+    def _get_deploy_split_func(cls):  # noqa: GL08
+        if cls._DEPLOY_SPLIT_FUNC is None:
+            cls._DEPLOY_SPLIT_FUNC = RayWrapper.put(
+                PandasDataframeAxisPartition.deploy_splitting_func
+            )
+        return cls._DEPLOY_SPLIT_FUNC
+
+    @classmethod
+    def _get_drain_func(cls):  # noqa: GL08
+        if cls._DRAIN_FUNC is None:
+            cls._DRAIN_FUNC = RayWrapper.put(PandasDataframeAxisPartition.drain)
+        return cls._DRAIN_FUNC
+
     def __init__(
         self,
         list_of_partitions,
@@ -169,7 +189,7 @@ def deploy_splitting_func(
             if extract_metadata
             else num_splits,
         ).remote(
-            _DEPLOY_SPLIT_FUNC,
+            cls._get_deploy_split_func(),
             axis,
             func,
             f_args,
@@ -230,7 +250,7 @@ def deploy_axis_func(
             * (1 + cls._PARTITIONS_METADATA_LEN),
             **({"max_retries": max_retries} if max_retries is not None else {}),
         ).remote(
-            _DEPLOY_AXIS_FUNC,
+            cls._get_deploy_axis_func(),
             axis,
             func,
             f_args,
@@ -486,7 +506,7 @@ def drain_call_queue(self, num_splits=None):
             _ = self.list_of_blocks
             return
         drained = super(PandasOnRayDataframeVirtualPartition, self).apply(
-            _DRAIN, num_splits=num_splits, call_queue=self.call_queue
+            self._get_drain_func(), num_splits=num_splits, call_queue=self.call_queue
         )
         self._list_of_block_partitions = drained
         self.call_queue = []
diff --git a/modin/core/execution/unidist/implementations/pandas_on_unidist/partitioning/virtual_partition.py b/modin/core/execution/unidist/implementations/pandas_on_unidist/partitioning/virtual_partition.py
--- a/modin/core/execution/unidist/implementations/pandas_on_unidist/partitioning/virtual_partition.py
+++ b/modin/core/execution/unidist/implementations/pandas_on_unidist/partitioning/virtual_partition.py
@@ -25,16 +25,6 @@
 from modin.utils import _inherit_docstrings
 
 
-# If unidist has not been initialized yet by Modin,
-# unidist itself handles initialization when calling `unidist.put`,
-# which is called inside of `UnidistWrapper.put`.
-_DEPLOY_AXIS_FUNC = UnidistWrapper.put(PandasDataframeAxisPartition.deploy_axis_func)
-_DRAIN = UnidistWrapper.put(PandasDataframeAxisPartition.drain)
-_DEPLOY_SPLIT_FUNC = UnidistWrapper.put(
-    PandasDataframeAxisPartition.deploy_splitting_func
-)
-
-
 class PandasOnUnidistDataframeVirtualPartition(PandasDataframeAxisPartition):
     """
     The class implements the interface in ``PandasDataframeAxisPartition``.
@@ -62,6 +52,33 @@ class PandasOnUnidistDataframeVirtualPartition(PandasDataframeAxisPartition):
     instance_type = unidist.core.base.object_ref.ObjectRef
     axis = None
 
+    # these variables are intentionally initialized at runtime (see #6023)
+    _DEPLOY_AXIS_FUNC = None
+    _DEPLOY_SPLIT_FUNC = None
+    _DRAIN_FUNC = None
+
+    @classmethod
+    def _get_deploy_axis_func(cls):  # noqa: GL08
+        if cls._DEPLOY_AXIS_FUNC is None:
+            cls._DEPLOY_AXIS_FUNC = UnidistWrapper.put(
+                PandasDataframeAxisPartition.deploy_axis_func
+            )
+        return cls._DEPLOY_AXIS_FUNC
+
+    @classmethod
+    def _get_deploy_split_func(cls):  # noqa: GL08
+        if cls._DEPLOY_SPLIT_FUNC is None:
+            cls._DEPLOY_SPLIT_FUNC = UnidistWrapper.put(
+                PandasDataframeAxisPartition.deploy_splitting_func
+            )
+        return cls._DEPLOY_SPLIT_FUNC
+
+    @classmethod
+    def _get_drain_func(cls):  # noqa: GL08
+        if cls._DRAIN_FUNC is None:
+            cls._DRAIN_FUNC = UnidistWrapper.put(PandasDataframeAxisPartition.drain)
+        return cls._DRAIN_FUNC
+
     def __init__(
         self,
         list_of_partitions,
@@ -170,7 +187,7 @@ def deploy_splitting_func(
             if extract_metadata
             else num_splits,
         ).remote(
-            _DEPLOY_SPLIT_FUNC,
+            cls._get_deploy_split_func(),
             axis,
             func,
             f_args,
@@ -231,7 +248,7 @@ def deploy_axis_func(
             * (1 + cls._PARTITIONS_METADATA_LEN),
             **({"max_retries": max_retries} if max_retries is not None else {}),
         ).remote(
-            _DEPLOY_AXIS_FUNC,
+            cls._get_deploy_axis_func(),
             axis,
             func,
             f_args,
@@ -481,7 +498,7 @@ def drain_call_queue(self, num_splits=None):
             _ = self.list_of_blocks
             return
         drained = super(PandasOnUnidistDataframeVirtualPartition, self).apply(
-            _DRAIN, num_splits=num_splits, call_queue=self.call_queue
+            self._get_drain_func(), num_splits=num_splits, call_queue=self.call_queue
         )
         self._list_of_block_partitions = drained
         self.call_queue = []


Test patch:
diff --git a/modin/pandas/test/integrations/__init__.py b/modin/pandas/test/integrations/__init__.py
new file mode 100644
--- /dev/null
+++ b/modin/pandas/test/integrations/__init__.py
@@ -0,0 +1,12 @@
+# Licensed to Modin Development Team under one or more contributor license agreements.
+# See the NOTICE file distributed with this work for additional information regarding
+# copyright ownership.  The Modin Development Team licenses this file to you under the
+# Apache License, Version 2.0 (the "License"); you may not use this file except in
+# compliance with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under
+# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
+# ANY KIND, either express or implied. See the License for the specific language
+# governing permissions and limitations under the License.
diff --git a/modin/pandas/test/integrations/test_lazy_import.py b/modin/pandas/test/integrations/test_lazy_import.py
new file mode 100644
--- /dev/null
+++ b/modin/pandas/test/integrations/test_lazy_import.py
@@ -0,0 +1,22 @@
+# Licensed to Modin Development Team under one or more contributor license agreements.
+# See the NOTICE file distributed with this work for additional information regarding
+# copyright ownership.  The Modin Development Team licenses this file to you under the
+# Apache License, Version 2.0 (the "License"); you may not use this file except in
+# compliance with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under
+# the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
+# ANY KIND, either express or implied. See the License for the specific language
+# governing permissions and limitations under the License.
+
+import lazy_import
+
+pandas = lazy_import.lazy_module("pandas")
+pyarrow = lazy_import.lazy_module("pyarrow")
+from modin import pandas as pd  # noqa: E402
+
+
+def test_dataframe_constructor():
+    pd.DataFrame({"col1": [1, 2, 3], "col2": list("abc")})