diff --git a/CHANGES.md b/CHANGES.md index c9b3d9384..91f57174c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,9 @@ dev === +- Fix a side effect altering dynamic modules at pickling time. + ([PR #426](https://github.com/cloudpipe/cloudpickle/pull/426)) + - Support for pickling type annotations on Python 3.10 as per [PEP 563]( https://www.python.org/dev/peps/pep-0563/) ([PR #400](https://github.com/cloudpipe/cloudpickle/pull/400)) diff --git a/cloudpickle/cloudpickle_fast.py b/cloudpickle/cloudpickle_fast.py index 46a9540ec..be1bb83c0 100644 --- a/cloudpickle/cloudpickle_fast.py +++ b/cloudpickle/cloudpickle_fast.py @@ -342,8 +342,13 @@ def _module_reduce(obj): if _is_importable(obj): return subimport, (obj.__name__,) else: - obj.__dict__.pop('__builtins__', None) - return dynamic_subimport, (obj.__name__, vars(obj)) + # Some external libraries can populate the "__builtins__" entry of a + # module's `__dict__` with unpicklable objects (see #316). For that + # reason, we do not attempt to pickle the "__builtins__" entry, and + # restore a default value for it at unpickling time. + state = obj.__dict__.copy() + state.pop('__builtins__', None) + return dynamic_subimport, (obj.__name__, state) def _method_reduce(obj): diff --git a/tests/cloudpickle_test.py b/tests/cloudpickle_test.py index f63d5168c..57a2ca0f4 100644 --- a/tests/cloudpickle_test.py +++ b/tests/cloudpickle_test.py @@ -604,6 +604,12 @@ def __reduce__(self): assert hasattr(depickled_mod.__builtins__, "abs") assert depickled_mod.f(-1) == 1 + # Additional check testing that the issue #425 is fixed: without the + # fix for #425, `mod.f` would not have access to `__builtins__`, and + # thus calling `mod.f(-1)` (which relies on the `abs` builtin) would + # fail. + assert mod.f(-1) == 1 + def test_load_dynamic_module_in_grandchild_process(self): # Make sure that when loaded, a dynamic module preserves its dynamic # property. Otherwise, this will lead to an ImportError if pickled in