-
-
Notifications
You must be signed in to change notification settings - Fork 33.7k
Description
Bug report
When pickling and unpickling an instance of an instance of a metaclass, _pickle reaches into the class object directly and calls tp_new from the C side, whereas pickle uses cls.__new__, which triggers a custom __getattribute__ if defined. Whether this is a bug or just an intentional optimization I'm not sure but at the very least one there is an observable difference in behavior in that unpickling from a distribution where _pickle is available does not call __getattribute__ but unpickling from a distribution where _pickle is not available does call __getattribute__.
MWE
(Works on all available CPythons on Compiler Explorer, 3.5 - 3.11)
https://godbolt.org/z/x76bs8dzv
To simulate different distributions having _pickle available or not, we can use a meta path entry to cause it to fail to import, causing pickle to fall back to the pure Python version.
import importlib.abc
import sys
class HideModuleFinder(importlib.abc.MetaPathFinder):
def __init__(self, hidden):
self.hidden = set(hidden)
def find_spec(self, fullname, path, target=None):
if fullname in self.hidden:
raise ImportError("Module is hidden")
return None # let next finder try
def install(hidden):
sys.meta_path.insert(0, HideModuleFinder(hidden))
hide_pickle = False # change me for testing different behavior
if hide_pickle:
install({"_pickle"})
import pickle # must be done after meta path hook
class Meta(type):
def __getattribute__(self, item):
print("__getattribute__ called with", item)
return type.__getattribute__(self, item)
MyClass = Meta("MyClass", (), {})
obj = MyClass()
print("PICKLING")
obj_str = pickle.dumps(obj)
print("UNPICKLING")
new_obj = pickle.loads(obj_str)Output with hide_pickle = True
PICKLING
__getattribute__ called with __reduce__
__getattribute__ called with __dict__
__getattribute__ called with __slots__
__getattribute__ called with __new__
__getattribute__ called with __qualname__
__getattribute__ called with __module__
UNPICKLING
__getattribute__ called with __new__
Output with hide_pickle = False
PICKLING
__getattribute__ called with __reduce__
__getattribute__ called with __dict__
__getattribute__ called with __slots__
__getattribute__ called with __qualname__
__getattribute__ called with __module__
UNPICKLING
Metadata
Metadata
Assignees
Labels
Projects
Status