Created on 2000-09-15 23:22 by gward, last changed 2000-09-17 12:21 by tim_one. This issue is now closed.
|msg1401 - (view)||Author: Greg Ward (gward)||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_one) *||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.