7777_DYNAMIC_CLASS_TRACKER_BY_ID = weakref .WeakValueDictionary ()
7878_DYNAMIC_CLASS_TRACKER_LOCK = threading .Lock ()
7979
80+ PYPY = platform .python_implementation () == "PyPy"
81+
82+ builtin_code_type = None
83+ if PYPY :
84+ # builtin-code objects only exist in pypy
85+ builtin_code_type = type (float .__new__ .__code__ )
86+
8087if sys .version_info [0 ] < 3 : # pragma: no branch
8188 from pickle import Pickler
8289 try :
@@ -453,12 +460,40 @@ def save_function(self, obj, name=None):
453460 Determines what kind of function obj is (e.g. lambda, defined at
454461 interactive prompt, etc) and handles the pickling appropriately.
455462 """
456- if not _is_global (obj , name = name ):
463+ if _is_global (obj , name = name ):
464+ return Pickler .save_global (self , obj , name = name )
465+ elif PYPY and isinstance (obj .__code__ , builtin_code_type ):
466+ return self .save_pypy_builtin_func (obj )
467+ else :
457468 return self .save_function_tuple (obj )
458- return Pickler .save_global (self , obj , name = name )
459469
460470 dispatch [types .FunctionType ] = save_function
461471
472+ def save_pypy_builtin_func (self , obj ):
473+ """Save pypy equivalent of builtin functions.
474+
475+ PyPy does not have the concept of builtin-functions. Instead,
476+ builtin-functions are simple function instances, but with a
477+ builtin-code attribute.
478+ Most of the time, builtin functions should be pickled by attribute. But
479+ PyPy has flaky support for __qualname__, so some builtin functions such
480+ as float.__new__ will be classified as dynamic. For this reason only,
481+ we created this special routine. Because builtin-functions are not
482+ expected to have closure or globals, there is no additional hack
483+ (compared the one already implemented in pickle) to protect ourselves
484+ from reference cycles. A simple (reconstructor, newargs, obj.__dict__)
485+ tuple is save_reduced.
486+
487+ Note also that PyPy improved their support for __qualname__ in v3.6, so
488+ this routing should be removed when cloudpickle supports only PyPy 3.6
489+ and later.
490+ """
491+ rv = (types .FunctionType , (obj .__code__ , {}, obj .__name__ ,
492+ obj .__defaults__ , obj .__closure__ ),
493+ obj .__dict__ )
494+ self .save_reduce (* rv , obj = obj )
495+
496+
462497 def _save_subimports (self , code , top_level_dependencies ):
463498 """
464499 Save submodules used by a function but not listed in its globals.
@@ -676,10 +711,7 @@ def save_function_tuple(self, func):
676711 write (pickle .TUPLE )
677712 write (pickle .REDUCE ) # applies _fill_function on the tuple
678713
679- _extract_code_globals_cache = (
680- weakref .WeakKeyDictionary ()
681- if not hasattr (sys , "pypy_version_info" )
682- else {})
714+ _extract_code_globals_cache = weakref .WeakKeyDictionary ()
683715
684716 @classmethod
685717 def extract_code_globals (cls , co ):
@@ -688,19 +720,14 @@ def extract_code_globals(cls, co):
688720 """
689721 out_names = cls ._extract_code_globals_cache .get (co )
690722 if out_names is None :
691- try :
692- names = co .co_names
693- except AttributeError :
694- # PyPy "builtin-code" object
695- out_names = set ()
696- else :
697- out_names = {names [oparg ] for _ , oparg in _walk_global_ops (co )}
698-
699- # see if nested function have any global refs
700- if co .co_consts :
701- for const in co .co_consts :
702- if type (const ) is types .CodeType :
703- out_names |= cls .extract_code_globals (const )
723+ names = co .co_names
724+ out_names = {names [oparg ] for _ , oparg in _walk_global_ops (co )}
725+
726+ # see if nested function have any global refs
727+ if co .co_consts :
728+ for const in co .co_consts :
729+ if isinstance (const , types .CodeType ):
730+ out_names |= cls .extract_code_globals (const )
704731
705732 cls ._extract_code_globals_cache [co ] = out_names
706733
0 commit comments