58
58
import weakref
59
59
import uuid
60
60
import threading
61
+ import typing
61
62
from enum import Enum
62
63
63
64
from pickle import _Pickler as Pickler
@@ -116,7 +117,7 @@ def _whichmodule(obj, name):
116
117
- Errors arising during module introspection are ignored, as those errors
117
118
are considered unwanted side effects.
118
119
"""
119
- module_name = getattr (obj , '__module__' , None )
120
+ module_name = _get_module_attr (obj )
120
121
if module_name is not None :
121
122
return module_name
122
123
# Protect the iteration by using a copy of sys.modules against dynamic
@@ -139,22 +140,46 @@ def _whichmodule(obj, name):
139
140
return None
140
141
141
142
142
- def _is_global (obj , name = None ):
143
+ if sys .version_info [:2 ] < (3 , 7 ): # pragma: no branch
144
+ # Workaround bug in old Python versions: prior to Python 3.7, T.__module__
145
+ # would always be set to "typing" even when the TypeVar T would be defined
146
+ # in a different module.
147
+ #
148
+ # For such older Python versions, we ignore the __module__ attribute of
149
+ # TypeVar instances and instead exhaustively lookup those instances in all
150
+ # currently imported modules via the _whichmodule function.
151
+ def _get_module_attr (obj ):
152
+ if isinstance (obj , typing .TypeVar ):
153
+ return None
154
+ return getattr (obj , '__module__' , None )
155
+ else :
156
+ def _get_module_attr (obj ):
157
+ return getattr (obj , '__module__' , None )
158
+
159
+
160
+ def _is_importable_by_name (obj , name = None ):
143
161
"""Determine if obj can be pickled as attribute of a file-backed module"""
162
+ return _lookup_module_and_qualname (obj , name = name ) is not None
163
+
164
+
165
+ def _lookup_module_and_qualname (obj , name = None ):
144
166
if name is None :
145
167
name = getattr (obj , '__qualname__' , None )
146
- if name is None :
168
+ if name is None : # pragma: no cover
169
+ # This used to be needed for Python 2.7 support but is probably not
170
+ # needed anymore. However we keep the __name__ introspection in case
171
+ # users of cloudpickle rely on this old behavior for unknown reasons.
147
172
name = getattr (obj , '__name__' , None )
148
173
149
174
module_name = _whichmodule (obj , name )
150
175
151
176
if module_name is None :
152
177
# In this case, obj.__module__ is None AND obj was not found in any
153
178
# imported module. obj is thus treated as dynamic.
154
- return False
179
+ return None
155
180
156
181
if module_name == "__main__" :
157
- return False
182
+ return None
158
183
159
184
module = sys .modules .get (module_name , None )
160
185
if module is None :
@@ -163,18 +188,20 @@ def _is_global(obj, name=None):
163
188
# types.ModuleType. The other possibility is that module was removed
164
189
# from sys.modules after obj was created/imported. But this case is not
165
190
# supported, as the standard pickle does not support it either.
166
- return False
191
+ return None
167
192
168
193
# module has been added to sys.modules, but it can still be dynamic.
169
194
if _is_dynamic (module ):
170
- return False
195
+ return None
171
196
172
197
try :
173
198
obj2 , parent = _getattribute (module , name )
174
199
except AttributeError :
175
200
# obj was not found inside the module it points to
176
- return False
177
- return obj2 is obj
201
+ return None
202
+ if obj2 is not obj :
203
+ return None
204
+ return module , name
178
205
179
206
180
207
def _extract_code_globals (co ):
@@ -418,6 +445,11 @@ def dump(self, obj):
418
445
else :
419
446
raise
420
447
448
+ def save_typevar (self , obj ):
449
+ self .save_reduce (* _typevar_reduce (obj ))
450
+
451
+ dispatch [typing .TypeVar ] = save_typevar
452
+
421
453
def save_memoryview (self , obj ):
422
454
self .save (obj .tobytes ())
423
455
@@ -467,7 +499,7 @@ def save_function(self, obj, name=None):
467
499
Determines what kind of function obj is (e.g. lambda, defined at
468
500
interactive prompt, etc) and handles the pickling appropriately.
469
501
"""
470
- if _is_global (obj , name = name ):
502
+ if _is_importable_by_name (obj , name = name ):
471
503
return Pickler .save_global (self , obj , name = name )
472
504
elif PYPY and isinstance (obj .__code__ , builtin_code_type ):
473
505
return self .save_pypy_builtin_func (obj )
@@ -770,7 +802,7 @@ def save_global(self, obj, name=None, pack=struct.pack):
770
802
_builtin_type , (_BUILTIN_TYPE_NAMES [obj ],), obj = obj )
771
803
elif name is not None :
772
804
Pickler .save_global (self , obj , name = name )
773
- elif not _is_global (obj , name = name ):
805
+ elif not _is_importable_by_name (obj , name = name ):
774
806
self .save_dynamic_class (obj )
775
807
else :
776
808
Pickler .save_global (self , obj , name = name )
@@ -1214,3 +1246,25 @@ def _is_dynamic(module):
1214
1246
else :
1215
1247
pkgpath = None
1216
1248
return _find_spec (module .__name__ , pkgpath , module ) is None
1249
+
1250
+
1251
+ def _make_typevar (name , bound , constraints , covariant , contravariant ):
1252
+ return typing .TypeVar (
1253
+ name , * constraints , bound = bound ,
1254
+ covariant = covariant , contravariant = contravariant
1255
+ )
1256
+
1257
+
1258
+ def _decompose_typevar (obj ):
1259
+ return (
1260
+ obj .__name__ , obj .__bound__ , obj .__constraints__ ,
1261
+ obj .__covariant__ , obj .__contravariant__ ,
1262
+ )
1263
+
1264
+
1265
+ def _typevar_reduce (obj ):
1266
+ # TypeVar instances have no __qualname__ hence we pass the name explicitly.
1267
+ module_and_name = _lookup_module_and_qualname (obj , name = obj .__name__ )
1268
+ if module_and_name is None :
1269
+ return (_make_typevar , _decompose_typevar (obj ))
1270
+ return (getattr , module_and_name )
0 commit comments