Issue 214553: copy.deepcopy() barfs if it hits class/function/method (original) (raw)

The 'copy.deepcopy()' function blows up if it stumbles across class, function, or method objects as it recurses into objects. This is (at least partly) because of an inconsistency between the dispatch table used for plain-vanilla 'copy()' and for 'deepcopy()': the 'copy()' dispatch table has an entry for ClassType, but the 'deepcopy()' dispatch table does not.

Also, neither dispatch table has entries for FunctionType or MethodType. This isn't a big deal for 'copy()', since it's unlikely that you'd do 'copy(my_func)', but it's inconsistent since you can do 'copy(my_class)' -- and the class object my_class is not copied, it's treated atomically, like strings or ints. For consistency, functions and methods should probably be treated the same as class objects.

Here's a script that demonstrates the problem:

from copy import copy, deepcopy

def f(): pass class K: pass

can copy a list that contains a function, since we don't

dive into the list

l = [1, 2, f] try: copy(l) except: print "error copying list with function" else: print "ok copying list with function"

but we can't copy the function

try: copy(f) except: print "error copying function" else: print "ok copying function"

also can't deep copy a list with a function because deepcopy() dives in

try: deepcopy(l) except: print "error deep-copying list with function" else: print "ok deep-copying list with function"

nor can we deepcopy the function alone

try: deepcopy(f) except: print "error deep-copying function" else: print "ok deep-copying function"

can copy list with class object

l = ["foo", K] try: copy(l) except: print "error copying list with class object" else: print "ok copying list with class object"

but can't deepcopy it

try: deepcopy(l) except: print "error deep-copying list with class object" else: print "ok deep-copying list with class object"

(Hmm, I don't see a test script for the 'copy' module -- perhaps this would be a good start.)

This gives the same output for me with Python 1.5.2, 1.6, and 2.0b1 (all on Linux):

ok copying list with function error copying function error deep-copying list with function error deep-copying function ok copying list with class object error deep-copying list with class object

And here's a patch that makes everything come out OK:

--- copy.py.orig Fri Sep 15 19:19:05 2000 +++ copy.py.hacked Fri Sep 15 19:20:20 2000 @@ -92,6 +92,8 @@ d[types.TypeType] = _copy_atomic d[types.XRangeType] = _copy_atomic d[types.ClassType] = _copy_atomic +d[types.FunctionType] = _copy_atomic +d[types.MethodType] = _copy_atomic

def _copy_list(x): return x[:] @@ -165,6 +167,9 @@ d[types.CodeType] = _deepcopy_atomic d[types.TypeType] = _deepcopy_atomic d[types.XRangeType] = _deepcopy_atomic +d[types.ClassType] = _deepcopy_atomic +d[types.FunctionType] = _deepcopy_atomic +d[types.MethodType] = _deepcopy_atomic

def _deepcopy_list(x, memo): y = []

Changed to Feature Request and added to PEP 42.

The docs (both the library ref and the comments at the top of copy.py) say that objects of class, method and function type are not handled by the module, so the docs match the code, and anything beyond that is a new-feature request. Well, almost -- it's a bug that shallow copies do "work" for class type now (since the docs say it doesn't), but I'm not going to take that away now.

If/when copy/deepcopy are so extended, more is needed than the suggested patch: after

y = deepcopy(x)

it should be the case that no mutations of x are visible via y, so _deepcopy_atomic is inadequate for anything with a writable attribute. For example, after deepcopy'ing a class object, changing the bases attr of the original should have no effect on the deepcopied clone.

So correct deepcopy implementations for these things probably need to use the "new" module. I suspect the copy module punted on these types to begin with in part because "new" wasn't built by default in the past.