classification
Title: copy.deepcopy() barfs if it hits class/function/method
Type: enhancement Stage:
Components: Library (Lib) Versions:
process
Status: closed Resolution: later
Dependencies: Superseder:
Assigned To: Nosy List: gward, tim.peters
Priority: normal Keywords:

Created on 2000-09-15 23:22 by gward, last changed 2000-09-17 12:21 by tim.peters. This issue is now closed.

Messages (2)
msg1401 - (view) Author: Greg Ward (gward) (Python committer) Date: 2000-09-15 23:22
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 = []
msg1402 - (view) Author: Tim Peters (tim.peters) * (Python committer) Date: 2000-09-17 12:21
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.
History
Date User Action Args
2000-09-15 23:22:04gwardcreate