如何覆盖 Python 对象的复制/深拷贝操作?

我明白在复制模块中 copydeepcopy的区别。我以前成功地使用过 copy.copycopy.deepcopy,但这是我第一次实际上重载 __copy____deepcopy__方法。我已经用谷歌搜索了一遍并查看了内置的 Python 模块,寻找 __copy____deepcopy__函数的实例(例如 sets.pydecimal.pydeepcopy0) ,但是我仍然不能100% 确定我是正确的。

我的设想是这样的:

我有一个配置对象。首先,我将用一组默认值实例化一个配置对象。这个配置将传递给多个其他对象(以确保所有对象都以相同的配置开始)。然而,一旦用户交互开始,每个对象都需要独立地调整其配置,而不影响其他对象的配置(这意味着我需要制作初始配置的深拷贝以便传递)。

下面是一个示例对象:

class ChartConfig(object):


def __init__(self):


#Drawing properties (Booleans/strings)
self.antialiased = None
self.plot_style = None
self.plot_title = None
self.autoscale = None


#X axis properties (strings/ints)
self.xaxis_title = None
self.xaxis_tick_rotation = None
self.xaxis_tick_align = None


#Y axis properties (strings/ints)
self.yaxis_title = None
self.yaxis_tick_rotation = None
self.yaxis_tick_align = None


#A list of non-primitive objects
self.trace_configs = []


def __copy__(self):
pass


def __deepcopy__(self, memo):
pass

在这个对象上实现 copydeepcopy方法以确保 copy.copycopy.deepcopy给我正确的行为的正确方法是什么?

91452 次浏览

我可能对细节有点不太了解,但是我要说的是

copy文件;

  • 浅表副本构造一个新的复合对象,然后(尽可能地)向其中插入对原始对象的引用。
  • 深度副本构造一个新的复合对象,然后递归地将原始对象的副本插入其中。

换句话说: copy()将只复制 top 元素,并将其余元素作为指向原始结构的指针。deepcopy()将递归地复制所有内容。

也就是说,deepcopy()就是你需要的。

如果您需要做一些真正具体的事情,您可以覆盖 __copy__()__deepcopy__(),如手册中所述。就我个人而言,我可能会实现一个简单的函数(例如 config.copy_config()或者类似的函数)来表明它不是 Python 的标准行为。

定制化的建议在 文件页的最后:

类可以使用相同的接口来 控制复制 控制酸洗。请参阅说明 模块泡菜的信息 这些方法。复制模块执行 不使用 copy _ reg 注册 模组。

为了让类定义自己的 复制实现,它可以定义 特殊方法 __copy__()和 前者被调用来实现浅表副本 操作; 没有其他参数 后者被调用 实现深度复制操作; 它 通过了一个论点,备忘录 字典。如果 __deepcopy__() 实施时需要深入研究 组件的副本,它应该调用 函数的 deepcopy()函数 组件作为第一个参数,并且 备忘录字典作为第二个参数。

由于您似乎并不关心 pickle 定制,因此定义 __copy____deepcopy__无疑似乎是适合您的正确方法。

具体来说,__copy__(浅拷贝)在您的情况下非常容易... ... :

def __copy__(self):
newone = type(self)()
newone.__dict__.update(self.__dict__)
return newone

__deepcopy__类似(也接受一个 memo参数) ,但是在返回之前,它必须调用 self.foo = deepcopy(self.foo, memo)来获取任何需要深度复制的属性 self.foo(本质上是容器属性——列表、字典、非原始对象,它们通过 __dict__保存其他东西)。

把 Alex Martelli 的回答和 Rob Young 的评论放在一起,你会得到以下代码:

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

指纹

init
11 [2, 3, 4, 5]
11 [2, 3, 4]

在这里,__deepcopy__填充 memo结构,以避免在从其成员引用对象本身的情况下进行过度复制。

从您的问题中还不清楚为什么需要重写这些方法,因为您不想对复制方法进行任何自定义。

无论如何,如果你确实想定制深度拷贝(例如,通过共享一些属性和复制其他属性) ,这里有一个解决方案:

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

Peter 的回答很棒之后,实现一个自定义深拷贝,对默认实现进行最小程度的修改(例如,像我需要的那样修改一个字段) :

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

在 Antony Hatchkins 干净利落的回答的基础上,下面是我的版本,其中所讨论的类来自另一个自定义类(我们需要称为 super) :

class Foo(FooBase):
def __init__(self, param1, param2):
self._base_params = [param1, param2]
super(Foo, result).__init__(*self._base_params)


def __copy__(self):
cls = self.__class__
result = cls.__new__(cls)
result.__dict__.update(self.__dict__)
super(Foo, result).__init__(*self._base_params)
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, copy.deepcopy(v, memo))
super(Foo, result).__init__(*self._base_params)
return result

copy模块最终使用 __getstate__()/__setstate__() 腌渍程序,因此这些也是要重写的有效目标。

默认实现只是返回并设置类的 __dict__,所以您不必调用 super()并担心 Eino Gourdin 的聪明技巧 以上

Peter 和 Eino Gourdin的回答很聪明也很有用,但是他们有一个非常微妙的缺陷!

Python 方法绑定到它们的对象。当执行 cp.__deepcopy__ = deepcopy_method时,实际上给出的是对象 cp 对... 的参考 __deepcopy__ 在原始物体上。任何对 cp.__deepcopy__的调用都会返回一个原始的 < strong > 副本! 如果你深拷贝你的对象,然后 复印副本,输出是一个 < strong > NOT 的拷贝!

下面是这种行为的一个最小示例,以及我的固定实现,您可以在其中复制 __deepcopy__实现,然后将其绑定到新对象:

from copy import deepcopy
import types




class Good:
def __init__(self):
self.i = 0


def __deepcopy__(self, memo):
deepcopy_method = self.__deepcopy__
self.__deepcopy__ = None
cp = deepcopy(self, memo)
self.__deepcopy__ = deepcopy_method
# Copy the function object
func = types.FunctionType(
deepcopy_method.__code__,
deepcopy_method.__globals__,
deepcopy_method.__name__,
deepcopy_method.__defaults__,
deepcopy_method.__closure__,
)
# Bind to cp and set
bound_method = func.__get__(cp, cp.__class__)
cp.__deepcopy__ = bound_method


return cp




class Bad:
def __init__(self):
self.i = 0


def __deepcopy__(self, memo):
deepcopy_method = self.__deepcopy__
self.__deepcopy__ = None
cp = deepcopy(self, memo)
self.__deepcopy__ = deepcopy_method
cp.__deepcopy__ = deepcopy_method
return cp




x = Bad()
copy = deepcopy(x)
copy.i = 1
copy_of_copy = deepcopy(copy)
print(copy_of_copy.i)  # 0


x = Good()
copy = deepcopy(x)
copy.i = 1
copy_of_copy = deepcopy(copy)
print(copy_of_copy.i)  # 1

我来这里是为了表演。使用默认的 copy.deepcopy()函数使我的代码减慢了30倍。 使用 回答 by@Anthony Hatchkins作为起点,我意识到 copy.deepcopy()对于列表来说真的很慢。我用简单的 [:]切片替换了 setattr循环,以复制整个列表。对于任何关心性能的人来说,值得进行 timeit.timeit()比较,并用更快的替代方案替换对 copy.deepcopy()的调用。

setup = 'import copy; l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]'
timeit.timeit(setup = setup, stmt='m=l[:]')
timeit.timeit(setup = setup, stmt='m=l.copy()')
timeit.timeit(setup = setup, stmt='m=copy.deepcopy(l)')

将产生以下结果:

0.11505379999289289
0.09126630000537261
6.423627900003339

Zach Price的想法类似,有一种更简单的方法来实现这个目标,即解除原来的 __deepcopy__方法的绑定,然后将其绑定到 cp

from copy import deepcopy
import types




class Good:
def __init__(self):
self.i = 0


def __deepcopy__(self, memo):
deepcopy_method = self.__deepcopy__
self.__deepcopy__ = None
cp = deepcopy(self, memo)
self.__deepcopy__ = deepcopy_method
        

# Bind to cp by types.MethodType
cp.__deepcopy__ = types.MethodType(deepcopy_method.__func__, cp)


return cp