python / cpython

The Python programming language
https://www.python.org
Other
63.6k stars 30.47k forks source link

maximum recursion depth in deepcopy regression #126817

Open tacaswell opened 1 week ago

tacaswell commented 1 week ago

Bug report

Bug description:

The following code works on py313 and below (this is a very cut-down version of some code in Matplotilb (matplotlib.path.Path)) but hit the maximum recursion depth on main.

# Add a code block here, if required
import copy

class Test:
    def __init__(self, a, b):
        self.a = a
        self.b = a
        self._state = True

    def __deepcopy__(self, memo=None):
        p = copy.deepcopy(super(), memo)
        p._state = False
        return p

t = Test(1, 2)
t2 = copy.deepcopy(t)

I bisected this to https://github.com/python/cpython/pull/125781/ and suspect the problem is that because it is returning a new object it is escaping the memoization and looping.

I'm happy to be told we should not be doing this fix it on the Matplotlib side, but I would like to be sure that this was an expected consequence of the change.

attn @serhiy-storchaka

CPython versions tested on:

3.14, CPython main branch

Operating systems tested on:

Linux

serhiy-storchaka commented 4 days ago

Interesting. I did not foresee such use case.

In general, such code worked by accident, because the code in the copy module is more permissive than in the pickle module. Although it does not work if the reducing function returns a string (enums or other singletons). It does not work if copying in the parent class was implemented by the __copy__ and __deepcopy__ methods.

The question is how to support that use case -- copying an object ignoring the special methods in the current class. Should we use super() for this? How to resolve conflict with different semantic for pickling? :thinking:

tacaswell commented 3 days ago

sounds like we should find another way downstream no matter which way CPython goes on this question?