How to override the copy/deepcopy operations for a Python object?
Its not clear from your problem why you need to override these methods, since you don't want to do any customization to the copying methods.
Anyhow, if you do want to customize the deep copy (e.g. by sharing some attributes and copying others), here is a solution:
from copy import deepcopy
def deepcopy_with_sharing(obj, shared_attribute_names, memo=None):
'''
Deepcopy an object, except for a given list of attributes, which should
be shared between the original object and its copy.
obj is some object
shared_attribute_names: A list of strings identifying the attributes that
should be shared between the original and its copy.
memo is the dictionary passed into __deepcopy__. Ignore this argument if
not calling from within __deepcopy__.
'''
assert isinstance(shared_attribute_names, (list, tuple))
shared_attributes = {k: getattr(obj, k) for k in shared_attribute_names}
if hasattr(obj, '__deepcopy__'):
# Do hack to prevent infinite recursion in call to deepcopy
deepcopy_method = obj.__deepcopy__
obj.__deepcopy__ = None
for attr in shared_attribute_names:
del obj.__dict__[attr]
clone = deepcopy(obj)
for attr, val in shared_attributes.iteritems():
setattr(obj, attr, val)
setattr(clone, attr, val)
if hasattr(obj, '__deepcopy__'):
# Undo hack
obj.__deepcopy__ = deepcopy_method
del clone.__deepcopy__
return clone
class A(object):
def __init__(self):
self.copy_me = []
self.share_me = []
def __deepcopy__(self, memo):
return deepcopy_with_sharing(self, shared_attribute_names = ['share_me'], memo=memo)
a = A()
b = deepcopy(a)
assert a.copy_me is not b.copy_me
assert a.share_me is b.share_me
c = deepcopy(b)
assert c.copy_me is not b.copy_me
assert c.share_me is b.share_me
The recommendations for customizing are at the very end of the docs page:
Classes can use the same interfaces to control copying that they use to control pickling. See the description of module pickle for information on these methods. The copy module does not use the copy_reg registration module.
In order for a class to define its own copy implementation, it can define special methods
__copy__()
and__deepcopy__()
. The former is called to implement the shallow copy operation; no additional arguments are passed. The latter is called to implement the deep copy operation; it is passed one argument, the memo dictionary. If the__deepcopy__()
implementation needs to make a deep copy of a component, it should call thedeepcopy()
function with the component as first argument and the memo dictionary as second argument.
Since you appear not to care about pickling customization, defining __copy__
and __deepcopy__
definitely seems like the right way to go for you.
Specifically, __copy__
(the shallow copy) is pretty easy in your case...:
def __copy__(self):
newone = type(self)()
newone.__dict__.update(self.__dict__)
return newone
__deepcopy__
would be similar (accepting a memo
arg too) but before the return it would have to call self.foo = deepcopy(self.foo, memo)
for any attribute self.foo
that needs deep copying (essentially attributes that are containers -- lists, dicts, non-primitive objects which hold other stuff through their __dict__
s).
Following Peter's excellent answer, to implement a custom deepcopy, with minimal alteration to the default implementation (e.g. just modifying a field like I needed) :
class Foo(object):
def __deepcopy__(self, memo):
deepcopy_method = self.__deepcopy__
self.__deepcopy__ = None
cp = deepcopy(self, memo)
self.__deepcopy__ = deepcopy_method
cp.__deepcopy__ = deepcopy_method
# custom treatments
# for instance: cp.id = None
return cp
Putting together Alex Martelli's answer and Rob Young's comment you get the following code:
from copy import copy, deepcopy
class A(object):
def __init__(self):
print 'init'
self.v = 10
self.z = [2,3,4]
def __copy__(self):
cls = self.__class__
result = cls.__new__(cls)
result.__dict__.update(self.__dict__)
return result
def __deepcopy__(self, memo):
cls = self.__class__
result = cls.__new__(cls)
memo[id(self)] = result
for k, v in self.__dict__.items():
setattr(result, k, deepcopy(v, memo))
return result
a = A()
a.v = 11
b1, b2 = copy(a), deepcopy(a)
a.v = 12
a.z.append(5)
print b1.v, b1.z
print b2.v, b2.z
prints
init
11 [2, 3, 4, 5]
11 [2, 3, 4]
here __deepcopy__
fills in the memo
dict to avoid excess copying in case the object itself is referenced from its member.