@@ -393,6 +393,71 @@ def _save_subimports(self, code, top_level_dependencies):
393393 # then discards the reference to it
394394 self .write (pickle .POP )
395395
396+ def save_dynamic_class (self , obj ):
397+ """
398+ Save a class that can't be stored as module global.
399+
400+ This method is used to serialize classes that are defined inside
401+ functions, or that otherwise can't be serialized as attribute lookups
402+ from global modules.
403+ """
404+ clsdict = dict (obj .__dict__ ) # copy dict proxy to a dict
405+ if not isinstance (clsdict .get ('__dict__' , None ), property ):
406+ # don't extract dict that are properties
407+ clsdict .pop ('__dict__' , None )
408+ clsdict .pop ('__weakref__' , None )
409+
410+ # hack as __new__ is stored differently in the __dict__
411+ new_override = clsdict .get ('__new__' , None )
412+ if new_override :
413+ clsdict ['__new__' ] = obj .__new__
414+
415+ save = self .save
416+ write = self .write
417+
418+ # We write pickle instructions explicitly here to handle the
419+ # possibility that the type object participates in a cycle with its own
420+ # __dict__. We first write an empty "skeleton" version of the class and
421+ # memoize it before writing the class' __dict__ itself. We then write
422+ # instructions to "rehydrate" the skeleton class by restoring the
423+ # attributes from the __dict__.
424+ #
425+ # A type can appear in a cycle with its __dict__ if an instance of the
426+ # type appears in the type's __dict__ (which happens for the stdlib
427+ # Enum class), or if the type defines methods that close over the name
428+ # of the type, (which is common for Python 2-style super() calls).
429+
430+ # Push the rehydration function.
431+ save (_rehydrate_skeleton_class )
432+
433+ # Mark the start of the args for the rehydration function.
434+ write (pickle .MARK )
435+
436+ # On PyPy, __doc__ is a readonly attribute, so we need to include it in
437+ # the initial skeleton class. This is safe because we know that the
438+ # doc can't participate in a cycle with the original class.
439+ doc_dict = {'__doc__' : clsdict .pop ('__doc__' , None )}
440+
441+ # Create and memoize an empty class with obj's name and bases.
442+ save (type (obj ))
443+ save ((
444+ obj .__name__ ,
445+ obj .__bases__ ,
446+ doc_dict ,
447+ ))
448+ write (pickle .REDUCE )
449+ self .memoize (obj )
450+
451+ # Now save the rest of obj's __dict__. Any references to obj
452+ # encountered while saving will point to the skeleton class.
453+ save (clsdict )
454+
455+ # Write a tuple of (skeleton_class, clsdict).
456+ write (pickle .TUPLE )
457+
458+ # Call _rehydrate_skeleton_class(skeleton_class, clsdict)
459+ write (pickle .REDUCE )
460+
396461 def save_function_tuple (self , func ):
397462 """ Pickles an actual func object.
398463
@@ -513,6 +578,12 @@ def save_builtin_function(self, obj):
513578 dispatch [types .BuiltinFunctionType ] = save_builtin_function
514579
515580 def save_global (self , obj , name = None , pack = struct .pack ):
581+ """
582+ Save a "global".
583+
584+ The name of this method is somewhat misleading: all types get
585+ dispatched here.
586+ """
516587 if obj .__module__ == "__builtin__" or obj .__module__ == "builtins" :
517588 if obj in _BUILTIN_TYPE_NAMES :
518589 return self .save_reduce (_builtin_type , (_BUILTIN_TYPE_NAMES [obj ],), obj = obj )
@@ -536,18 +607,7 @@ def save_global(self, obj, name=None, pack=struct.pack):
536607
537608 typ = type (obj )
538609 if typ is not obj and isinstance (obj , (type , types .ClassType )):
539- d = dict (obj .__dict__ ) # copy dict proxy to a dict
540- if not isinstance (d .get ('__dict__' , None ), property ):
541- # don't extract dict that are properties
542- d .pop ('__dict__' , None )
543- d .pop ('__weakref__' , None )
544-
545- # hack as __new__ is stored differently in the __dict__
546- new_override = d .get ('__new__' , None )
547- if new_override :
548- d ['__new__' ] = obj .__new__
549-
550- self .save_reduce (typ , (obj .__name__ , obj .__bases__ , d ), obj = obj )
610+ self .save_dynamic_class (obj )
551611 else :
552612 raise pickle .PicklingError ("Can't pickle %r" % obj )
553613
@@ -986,6 +1046,16 @@ def _make_skel_func(code, cell_count, base_globals=None):
9861046 return types .FunctionType (code , base_globals , None , None , closure )
9871047
9881048
1049+ def _rehydrate_skeleton_class (skeleton_class , class_dict ):
1050+ """Put attributes from `class_dict` back on `skeleton_class`.
1051+
1052+ See CloudPickler.save_dynamic_class for more info.
1053+ """
1054+ for attrname , attr in class_dict .items ():
1055+ setattr (skeleton_class , attrname , attr )
1056+ return skeleton_class
1057+
1058+
9891059def _find_module (mod_name ):
9901060 """
9911061 Iterate over each part instead of calling imp.find_module directly.
0 commit comments