@@ -322,7 +322,9 @@ def whichmodule(obj, name):
322
322
"""Find the module an object belong to."""
323
323
dotted_path = name .split ('.' )
324
324
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 :
326
328
# Protect the iteration by using a list copy of sys.modules against dynamic
327
329
# modules that trigger imports of other modules upon calls to getattr.
328
330
for module_name , module in sys .modules .copy ().items ():
@@ -336,22 +338,21 @@ def whichmodule(obj, name):
336
338
except AttributeError :
337
339
pass
338
340
module_name = '__main__'
339
- elif module_name is None :
340
- module_name = '__main__'
341
341
342
342
try :
343
343
__import__ (module_name , level = 0 )
344
344
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 :
345
348
if _getattribute (module , dotted_path ) is obj :
346
349
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 } " )
351
353
352
354
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 } " )
355
356
356
357
def encode_long (x ):
357
358
r"""Encode a long to a two's complement little-endian binary string.
@@ -403,6 +404,13 @@ def decode_long(data):
403
404
"""
404
405
return int .from_bytes (data , byteorder = 'little' , signed = True )
405
406
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
+
406
414
407
415
_NoValue = object ()
408
416
@@ -585,8 +593,7 @@ def save(self, obj, save_persistent_id=True):
585
593
if reduce is not _NoValue :
586
594
rv = reduce ()
587
595
else :
588
- raise PicklingError ("Can't pickle %r object: %r" %
589
- (t .__name__ , obj ))
596
+ raise PicklingError (f"Can't pickle { _T (t )} object" )
590
597
591
598
# Check for string returned by reduce(), meaning "save as global"
592
599
if isinstance (rv , str ):
@@ -595,13 +602,13 @@ def save(self, obj, save_persistent_id=True):
595
602
596
603
# Assert that reduce() returned a tuple
597
604
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 ) } ' )
599
606
600
607
# Assert that it returned an appropriately sized tuple
601
608
l = len (rv )
602
609
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" )
605
612
606
613
# Save the reduce() output and finally memoize the object
607
614
self .save_reduce (obj = obj , * rv )
@@ -626,10 +633,12 @@ def save_reduce(self, func, args, state=None, listitems=None,
626
633
dictitems = None , state_setter = None , * , obj = None ):
627
634
# This API is called by some subclasses
628
635
629
- if not isinstance (args , tuple ):
630
- raise PicklingError ("args from save_reduce() must be a tuple" )
631
636
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 )} " )
633
642
634
643
save = self .save
635
644
write = self .write
@@ -638,11 +647,10 @@ def save_reduce(self, func, args, state=None, listitems=None,
638
647
if self .proto >= 2 and func_name == "__newobj_ex__" :
639
648
cls , args , kwargs = args
640
649
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__" )
643
651
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 } " )
646
654
if self .proto >= 4 :
647
655
save (cls )
648
656
save (args )
@@ -682,11 +690,10 @@ def save_reduce(self, func, args, state=None, listitems=None,
682
690
# Python 2.2).
683
691
cls = args [0 ]
684
692
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__" )
687
694
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 } " )
690
697
args = args [1 :]
691
698
save (cls )
692
699
save (args )
@@ -1128,8 +1135,7 @@ def save_global(self, obj, name=None):
1128
1135
def _save_toplevel_by_name (self , module_name , name ):
1129
1136
if self .proto >= 3 :
1130
1137
# 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"
1133
1139
else :
1134
1140
if self .fix_imports :
1135
1141
r_name_mapping = _compat_pickle .REVERSE_NAME_MAPPING
@@ -1138,13 +1144,19 @@ def _save_toplevel_by_name(self, module_name, name):
1138
1144
module_name , name = r_name_mapping [(module_name , name )]
1139
1145
elif module_name in r_import_mapping :
1140
1146
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 } " )
1148
1160
1149
1161
def save_type (self , obj ):
1150
1162
if obj is type (None ):
@@ -1605,17 +1617,13 @@ def find_class(self, module, name):
1605
1617
elif module in _compat_pickle .IMPORT_MAPPING :
1606
1618
module = _compat_pickle .IMPORT_MAPPING [module ]
1607
1619
__import__ (module , level = 0 )
1608
- if self .proto >= 4 :
1609
- module = sys .modules [module ]
1620
+ if self .proto >= 4 and '.' in name :
1610
1621
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} " )
1614
1622
try :
1615
- return _getattribute (module , dotted_path )
1623
+ return _getattribute (sys . modules [ module ] , dotted_path )
1616
1624
except AttributeError :
1617
1625
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} " )
1619
1627
else :
1620
1628
return getattr (sys .modules [module ], name )
1621
1629
0 commit comments