-
- 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