|
42 | 42 | """ |
43 | 43 | from __future__ import print_function |
44 | 44 |
|
45 | | -import dis |
46 | | -from functools import partial |
47 | | -import imp |
48 | 45 | import io |
49 | | -import itertools |
50 | | -import logging |
| 46 | +import dis |
| 47 | +import sys |
| 48 | +import types |
51 | 49 | import opcode |
52 | | -import operator |
53 | 50 | import pickle |
54 | 51 | import struct |
55 | | -import sys |
56 | | -import traceback |
57 | | -import types |
| 52 | +import logging |
58 | 53 | import weakref |
| 54 | +import operator |
| 55 | +import importlib |
| 56 | +import itertools |
| 57 | +import traceback |
| 58 | +from functools import partial |
59 | 59 |
|
60 | 60 |
|
61 | 61 | # cloudpickle is meant for inter process communication: we expect all |
|
78 | 78 | PY3 = True |
79 | 79 |
|
80 | 80 |
|
| 81 | +# Container for the global namespace to ensure consistent unpickling of |
| 82 | +# functions defined in dynamic modules (modules not registed in sys.modules). |
| 83 | +_dynamic_modules_globals = weakref.WeakValueDictionary() |
| 84 | + |
| 85 | + |
| 86 | +class _DynamicModuleFuncGlobals(dict): |
| 87 | + """Global variables referenced by a function defined in a dynamic module |
| 88 | +
|
| 89 | + To avoid leaking references we store such context in a WeakValueDictionary |
| 90 | + instance. However instances of python builtin types such as dict cannot |
| 91 | + be used directly as values in such a construct, hence the need for a |
| 92 | + derived class. |
| 93 | + """ |
| 94 | + pass |
| 95 | + |
| 96 | + |
81 | 97 | def _make_cell_set_template_code(): |
82 | 98 | """Get the Python compiler to emit LOAD_FAST(arg); STORE_DEREF |
83 | 99 |
|
@@ -288,20 +304,10 @@ def save_module(self, obj): |
288 | 304 | """ |
289 | 305 | Save a module as an import |
290 | 306 | """ |
291 | | - mod_name = obj.__name__ |
292 | | - # If module is successfully found then it is not a dynamically created module |
293 | | - if hasattr(obj, '__file__'): |
294 | | - is_dynamic = False |
295 | | - else: |
296 | | - try: |
297 | | - _find_module(mod_name) |
298 | | - is_dynamic = False |
299 | | - except ImportError: |
300 | | - is_dynamic = True |
301 | | - |
302 | 307 | self.modules.add(obj) |
303 | | - if is_dynamic: |
304 | | - self.save_reduce(dynamic_subimport, (obj.__name__, vars(obj)), obj=obj) |
| 308 | + if _is_dynamic(obj): |
| 309 | + self.save_reduce(dynamic_subimport, (obj.__name__, vars(obj)), |
| 310 | + obj=obj) |
305 | 311 | else: |
306 | 312 | self.save_reduce(subimport, (obj.__name__,), obj=obj) |
307 | 313 |
|
@@ -566,7 +572,7 @@ def save_function_tuple(self, func): |
566 | 572 | 'name': func.__name__, |
567 | 573 | 'doc': func.__doc__, |
568 | 574 | } |
569 | | - if hasattr(func, '__annotations__'): |
| 575 | + if hasattr(func, '__annotations__') and sys.version_info >= (3, 7): |
570 | 576 | state['annotations'] = func.__annotations__ |
571 | 577 | if hasattr(func, '__qualname__'): |
572 | 578 | state['qualname'] = func.__qualname__ |
@@ -661,6 +667,13 @@ def save_global(self, obj, name=None, pack=struct.pack): |
661 | 667 | The name of this method is somewhat misleading: all types get |
662 | 668 | dispatched here. |
663 | 669 | """ |
| 670 | + if obj is type(None): |
| 671 | + return self.save_reduce(type, (None,), obj=obj) |
| 672 | + elif obj is type(Ellipsis): |
| 673 | + return self.save_reduce(type, (Ellipsis,), obj=obj) |
| 674 | + elif obj is type(NotImplemented): |
| 675 | + return self.save_reduce(type, (NotImplemented,), obj=obj) |
| 676 | + |
664 | 677 | if obj.__module__ == "__main__": |
665 | 678 | return self.save_dynamic_class(obj) |
666 | 679 |
|
@@ -933,7 +946,7 @@ def subimport(name): |
933 | 946 |
|
934 | 947 |
|
935 | 948 | def dynamic_subimport(name, vars): |
936 | | - mod = imp.new_module(name) |
| 949 | + mod = types.ModuleType(name) |
937 | 950 | mod.__dict__.update(vars) |
938 | 951 | return mod |
939 | 952 |
|
@@ -1090,12 +1103,18 @@ def _make_skel_func(code, cell_count, base_globals=None): |
1090 | 1103 | if base_globals is None: |
1091 | 1104 | base_globals = {} |
1092 | 1105 | elif isinstance(base_globals, str): |
1093 | | - if sys.modules.get(base_globals, None) is not None: |
1094 | | - # this checks if we can import the previous environment the object |
1095 | | - # lived in |
1096 | | - base_globals = vars(sys.modules[base_globals]) |
1097 | | - else: |
1098 | | - base_globals = {} |
| 1106 | + base_globals_name = base_globals |
| 1107 | + try: |
| 1108 | + # First try to reuse the globals from the module containing the |
| 1109 | + # function. If it is not possible to retrieve it, fallback to an |
| 1110 | + # empty dictionary. |
| 1111 | + base_globals = vars(importlib.import_module(base_globals)) |
| 1112 | + except ImportError: |
| 1113 | + base_globals = _dynamic_modules_globals.get( |
| 1114 | + base_globals_name, None) |
| 1115 | + if base_globals is None: |
| 1116 | + base_globals = _DynamicModuleFuncGlobals() |
| 1117 | + _dynamic_modules_globals[base_globals_name] = base_globals |
1099 | 1118 |
|
1100 | 1119 | base_globals['__builtins__'] = __builtins__ |
1101 | 1120 |
|
@@ -1125,19 +1144,31 @@ def _rehydrate_skeleton_class(skeleton_class, class_dict): |
1125 | 1144 | return skeleton_class |
1126 | 1145 |
|
1127 | 1146 |
|
1128 | | -def _find_module(mod_name): |
| 1147 | +def _is_dynamic(module): |
1129 | 1148 | """ |
1130 | | - Iterate over each part instead of calling imp.find_module directly. |
1131 | | - This function is able to find submodules (e.g. scikit.tree) |
| 1149 | + Return True if the module is special module that cannot be imported by its |
| 1150 | + name. |
1132 | 1151 | """ |
1133 | | - path = None |
1134 | | - for part in mod_name.split('.'): |
1135 | | - if path is not None: |
1136 | | - path = [path] |
1137 | | - file, path, description = imp.find_module(part, path) |
1138 | | - if file is not None: |
1139 | | - file.close() |
1140 | | - return path, description |
| 1152 | + # Quick check: module that have __file__ attribute are not dynamic modules. |
| 1153 | + if hasattr(module, '__file__'): |
| 1154 | + return False |
| 1155 | + |
| 1156 | + if hasattr(module, '__spec__'): |
| 1157 | + return module.__spec__ is None |
| 1158 | + else: |
| 1159 | + # Backward compat for Python 2 |
| 1160 | + import imp |
| 1161 | + try: |
| 1162 | + path = None |
| 1163 | + for part in module.__name__.split('.'): |
| 1164 | + if path is not None: |
| 1165 | + path = [path] |
| 1166 | + f, path, description = imp.find_module(part, path) |
| 1167 | + if f is not None: |
| 1168 | + f.close() |
| 1169 | + except ImportError: |
| 1170 | + return True |
| 1171 | + return False |
1141 | 1172 |
|
1142 | 1173 |
|
1143 | 1174 | """Constructors for 3rd party libraries |
|
0 commit comments