@@ -322,7 +322,9 @@ def whichmodule(obj, name):
322322 """Find the module an object belong to."""
323323 dotted_path = name .split ('.' )
324324 module_name = getattr (obj , '__module__' , None )
325- if module_name is None and '<locals>' not in dotted_path :
325+ if '<locals>' in dotted_path :
326+ raise PicklingError (f"Can't pickle local object { obj !r} " )
327+ if module_name is None :
326328 # Protect the iteration by using a list copy of sys.modules against dynamic
327329 # modules that trigger imports of other modules upon calls to getattr.
328330 for module_name , module in sys .modules .copy ().items ():
@@ -336,22 +338,21 @@ def whichmodule(obj, name):
336338 except AttributeError :
337339 pass
338340 module_name = '__main__'
339- elif module_name is None :
340- module_name = '__main__'
341341
342342 try :
343343 __import__ (module_name , level = 0 )
344344 module = sys .modules [module_name ]
345+ except (ImportError , ValueError , KeyError ) as exc :
346+ raise PicklingError (f"Can't pickle { obj !r} : { exc !s} " )
347+ try :
345348 if _getattribute (module , dotted_path ) is obj :
346349 return module_name
347- except (ImportError , KeyError , AttributeError ):
348- raise PicklingError (
349- "Can't pickle %r: it's not found as %s.%s" %
350- (obj , module_name , name )) from None
350+ except AttributeError :
351+ raise PicklingError (f"Can't pickle { obj !r} : "
352+ f"it's not found as { module_name } .{ name } " )
351353
352354 raise PicklingError (
353- "Can't pickle %r: it's not the same object as %s.%s" %
354- (obj , module_name , name ))
355+ f"Can't pickle { obj !r} : it's not the same object as { module_name } .{ name } " )
355356
356357def encode_long (x ):
357358 r"""Encode a long to a two's complement little-endian binary string.
@@ -403,6 +404,13 @@ def decode_long(data):
403404 """
404405 return int .from_bytes (data , byteorder = 'little' , signed = True )
405406
407+ def _T (obj ):
408+ cls = type (obj )
409+ module = cls .__module__
410+ if module in (None , 'builtins' , '__main__' ):
411+ return cls .__qualname__
412+ return f'{ module } .{ cls .__qualname__ } '
413+
406414
407415_NoValue = object ()
408416
@@ -585,8 +593,7 @@ def save(self, obj, save_persistent_id=True):
585593 if reduce is not _NoValue :
586594 rv = reduce ()
587595 else :
588- raise PicklingError ("Can't pickle %r object: %r" %
589- (t .__name__ , obj ))
596+ raise PicklingError (f"Can't pickle { _T (t )} object" )
590597
591598 # Check for string returned by reduce(), meaning "save as global"
592599 if isinstance (rv , str ):
@@ -595,13 +602,13 @@ def save(self, obj, save_persistent_id=True):
595602
596603 # Assert that reduce() returned a tuple
597604 if not isinstance (rv , tuple ):
598- raise PicklingError ("%s must return string or tuple" % reduce )
605+ raise PicklingError (f'__reduce__ must return a string or tuple, not { _T ( rv ) } ' )
599606
600607 # Assert that it returned an appropriately sized tuple
601608 l = len (rv )
602609 if not (2 <= l <= 6 ):
603- raise PicklingError ("Tuple returned by %s must have "
604- "two to six elements" % reduce )
610+ raise PicklingError ("tuple returned by __reduce__ "
611+ "must contain 2 through 6 elements" )
605612
606613 # Save the reduce() output and finally memoize the object
607614 self .save_reduce (obj = obj , * rv )
@@ -626,10 +633,12 @@ def save_reduce(self, func, args, state=None, listitems=None,
626633 dictitems = None , state_setter = None , * , obj = None ):
627634 # This API is called by some subclasses
628635
629- if not isinstance (args , tuple ):
630- raise PicklingError ("args from save_reduce() must be a tuple" )
631636 if not callable (func ):
632- raise PicklingError ("func from save_reduce() must be callable" )
637+ raise PicklingError (f"first item of the tuple returned by __reduce__ "
638+ f"must be callable, not { _T (func )} " )
639+ if not isinstance (args , tuple ):
640+ raise PicklingError (f"second item of the tuple returned by __reduce__ "
641+ f"must be a tuple, not { _T (args )} " )
633642
634643 save = self .save
635644 write = self .write
@@ -638,11 +647,10 @@ def save_reduce(self, func, args, state=None, listitems=None,
638647 if self .proto >= 2 and func_name == "__newobj_ex__" :
639648 cls , args , kwargs = args
640649 if not hasattr (cls , "__new__" ):
641- raise PicklingError ("args[0] from {} args has no __new__"
642- .format (func_name ))
650+ raise PicklingError ("first argument to __newobj_ex__() has no __new__" )
643651 if obj is not None and cls is not obj .__class__ :
644- raise PicklingError ("args[0] from {} args has the wrong class "
645- . format ( func_name ) )
652+ raise PicklingError (f"first argument to __newobj_ex__() "
653+ f"must be { obj . __class__ !r } , not { cls !r } " )
646654 if self .proto >= 4 :
647655 save (cls )
648656 save (args )
@@ -682,11 +690,10 @@ def save_reduce(self, func, args, state=None, listitems=None,
682690 # Python 2.2).
683691 cls = args [0 ]
684692 if not hasattr (cls , "__new__" ):
685- raise PicklingError (
686- "args[0] from __newobj__ args has no __new__" )
693+ raise PicklingError ("first argument to __newobj__() has no __new__" )
687694 if obj is not None and cls is not obj .__class__ :
688- raise PicklingError (
689- "args[0] from __newobj__ args has the wrong class " )
695+ raise PicklingError (f"first argument to __newobj__() "
696+ f"must be { obj . __class__ !r } , not { cls !r } " )
690697 args = args [1 :]
691698 save (cls )
692699 save (args )
@@ -1128,8 +1135,7 @@ def save_global(self, obj, name=None):
11281135 def _save_toplevel_by_name (self , module_name , name ):
11291136 if self .proto >= 3 :
11301137 # Non-ASCII identifiers are supported only with protocols >= 3.
1131- self .write (GLOBAL + bytes (module_name , "utf-8" ) + b'\n ' +
1132- bytes (name , "utf-8" ) + b'\n ' )
1138+ encoding = "utf-8"
11331139 else :
11341140 if self .fix_imports :
11351141 r_name_mapping = _compat_pickle .REVERSE_NAME_MAPPING
@@ -1138,13 +1144,19 @@ def _save_toplevel_by_name(self, module_name, name):
11381144 module_name , name = r_name_mapping [(module_name , name )]
11391145 elif module_name in r_import_mapping :
11401146 module_name = r_import_mapping [module_name ]
1141- try :
1142- self .write (GLOBAL + bytes (module_name , "ascii" ) + b'\n ' +
1143- bytes (name , "ascii" ) + b'\n ' )
1144- except UnicodeEncodeError :
1145- raise PicklingError (
1146- "can't pickle global identifier '%s.%s' using "
1147- "pickle protocol %i" % (module_name , name , self .proto )) from None
1147+ encoding = "ascii"
1148+ try :
1149+ self .write (GLOBAL + bytes (module_name , encoding ) + b'\n ' )
1150+ except UnicodeEncodeError :
1151+ raise PicklingError (
1152+ f"can't pickle module identifier { module_name !r} using "
1153+ f"pickle protocol { self .proto } " )
1154+ try :
1155+ self .write (bytes (name , encoding ) + b'\n ' )
1156+ except UnicodeEncodeError :
1157+ raise PicklingError (
1158+ f"can't pickle global identifier { name !r} using "
1159+ f"pickle protocol { self .proto } " )
11481160
11491161 def save_type (self , obj ):
11501162 if obj is type (None ):
@@ -1605,17 +1617,13 @@ def find_class(self, module, name):
16051617 elif module in _compat_pickle .IMPORT_MAPPING :
16061618 module = _compat_pickle .IMPORT_MAPPING [module ]
16071619 __import__ (module , level = 0 )
1608- if self .proto >= 4 :
1609- module = sys .modules [module ]
1620+ if self .proto >= 4 and '.' in name :
16101621 dotted_path = name .split ('.' )
1611- if '<locals>' in dotted_path :
1612- raise AttributeError (
1613- f"Can't get local attribute { name !r} on { module !r} " )
16141622 try :
1615- return _getattribute (module , dotted_path )
1623+ return _getattribute (sys . modules [ module ] , dotted_path )
16161624 except AttributeError :
16171625 raise AttributeError (
1618- f"Can't get attribute { name !r} on { module !r} " ) from None
1626+ f"Can't resolve path { name !r} on module { module !r} " )
16191627 else :
16201628 return getattr (sys .modules [module ], name )
16211629
0 commit comments