You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
If the __qualname__ of a class is set to have a circular reference to itself, pickle behaves differently based on protocol. Following script demonstrates the issue:
Python3.7:
sys.version_info(major=3, minor=7, micro=3, releaselevel='final', serial=0)
0: RecursionError('maximum recursion depth exceeded while pickling an object')
1: RecursionError('maximum recursion depth exceeded while pickling an object')
2: RecursionError('maximum recursion depth exceeded while pickling an object')
3: RecursionError('maximum recursion depth exceeded while pickling an object')
4: Dump OK, Load OK,
======================================================
This was introduced as a side effect of bpo-23611 (?). I can think of the following approaches to fix the issue and make the behavior consistent:
Check if the class has a self-reference and raise an error for all protocols.
Use memoization to handle self-references. I am not sure what should be dumped in this case. In the example above Foo will exist in the namespace but not Foo.ref.
Dump such classes similar to Python 2 pickle and Python 3 pickle protocol >= 4.
I had a stab at pickle.py and had a bit of success in doing point 3 above. Posting this issue for discussions. I would be happy to submit a PR for this issue.
Pickling unbound methods and nested classes is natively supported in protocol 4. In protocol 3 and lower, only top-level classes and functions (i.e. these that do not have a dot in __qualname__) were initially supported. Later support of nested names was implemented via the getattr() function (see bpo-23611). Foo.ref is now represented as getattr(Foo, 'ref'). The problem in your example is that if Foo.__qualname__ is 'Foo.ref', Foo is pickled as getattr(Foo, 'ref'), that involves infinite recursion.
This problem can be solved by forcing all parent objects to be serialized by name without using the normal dispatch mechanism: represent Foo.ref as getattr(getattr(module, 'Foo'), 'ref').
I consider this a bugfix because it matches the behavior in protocols < 4 with the behavior in protocols >= 4 which is used by default since 3.8 (see bpo-23403). I wish I did it from the beginning.
Activity
SaimRaza commentedon Nov 11, 2019
If the __qualname__ of a class is set to have a circular reference to itself, pickle behaves differently based on protocol. Following script demonstrates the issue:
======================================================
======================================================
OUTPUT:
Python2.7:
sys.version_info(major=2, minor=7, micro=16, releaselevel='final', serial=0)
0: Dump OK, Load OK,
1: Dump OK, Load OK,
2: Dump OK, Load OK,
Python3.7:
sys.version_info(major=3, minor=7, micro=3, releaselevel='final', serial=0)
0: RecursionError('maximum recursion depth exceeded while pickling an object')
1: RecursionError('maximum recursion depth exceeded while pickling an object')
2: RecursionError('maximum recursion depth exceeded while pickling an object')
3: RecursionError('maximum recursion depth exceeded while pickling an object')
4: Dump OK, Load OK,
======================================================
This was introduced as a side effect of bpo-23611 (?). I can think of the following approaches to fix the issue and make the behavior consistent:
Foowill exist in the namespace but notFoo.ref.I had a stab at pickle.py and had a bit of success in doing point 3 above. Posting this issue for discussions. I would be happy to submit a PR for this issue.
Thanks,
Saim Raza
furkanonder commentedon Apr 21, 2020
I ran your script and didn't get RecursionError. The issue seems to be fixed.
furkanonder commentedon Apr 21, 2020
Ahh. I misunderstood the problem. Pickle behaves differently when it is a circular reference. If you have a solution, I am waiting with curiosity.
pythongh-82951: Fix serializing by name in pickle protocols < 4
serhiy-storchaka commentedon Jul 23, 2024
Pickling unbound methods and nested classes is natively supported in protocol 4. In protocol 3 and lower, only top-level classes and functions (i.e. these that do not have a dot in
__qualname__) were initially supported. Later support of nested names was implemented via thegetattr()function (see bpo-23611).Foo.refis now represented asgetattr(Foo, 'ref'). The problem in your example is that ifFoo.__qualname__is'Foo.ref',Foois pickled asgetattr(Foo, 'ref'), that involves infinite recursion.This problem can be solved by forcing all parent objects to be serialized by name without using the normal dispatch mechanism: represent
Foo.refasgetattr(getattr(module, 'Foo'), 'ref').I consider this a bugfix because it matches the behavior in protocols < 4 with the behavior in protocols >= 4 which is used by default since 3.8 (see bpo-23403). I wish I did it from the beginning.
gh-82951: Fix serializing by name in pickle protocols < 4 (GH-122149)
pythongh-82951: Fix serializing by name in pickle protocols < 4 (pyth…
pythongh-82951: Fix serializing by name in pickle protocols < 4 (pyth…
[3.12] gh-82951: Fix serializing by name in pickle protocols < 4 (GH-…
[3.13] gh-82951: Fix serializing by name in pickle protocols < 4 (GH-…
pythongh-82951: Fix typo in pickle
pythongh-82951: Fix typo in the pickle error formatting code