121

copy我了解与deepcopy复制模块中的区别。我之前成功地使用过copy.copyand copy.deepcopy,但这是我第一次真正开始重载__copy__and__deepcopy__方法。我已经在 Google 上搜索并查看了内置的 Python 模块以查找__copy__and__deepcopy__函数的实例(例如sets.pydecimal.pyfractions.py),但我仍然不能 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给我正确的行为的正确方法是什么?

4

10 回答 10

118

将 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__填写memodict 以避免过度复制,以防对象本身被其成员引用。

于 2013-04-02T20:46:44.800 回答
92

自定义建议位于文档页面的最后:

类可以使用相同的接口来控制它们用来控制酸洗的复制。有关这些方法的信息,请参阅模块 pickle 的描述。复制模块不使用 copy_reg 注册模块。

为了让一个类定义它自己的复制实现,它可以定义特殊的方法__copy__()__deepcopy__(). 前者被调用来实现浅拷贝操作;没有传递额外的参数。后者被调用来实现深拷贝操作;它被传递一个参数,备忘录字典。如果__deepcopy__() 实现需要对组件进行深层复制,它应该deepcopy()以组件作为第一个参数和备忘录字典作为第二个参数来调用该函数。

由于您似乎不关心腌制定制,因此定义__copy__并且__deepcopy__绝对是适合您的正确方式。

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

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

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

于 2009-09-30T21:58:58.043 回答
19

按照Peter 的出色回答,实现自定义 deepcopy,对默认实现的改动最小(例如,只修改我需要的字段):

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
于 2016-11-08T10:13:07.613 回答
13

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

无论如何,如果您确实想自定义深层副本(例如,通过共享一些属性并复制其他属性),这里有一个解决方案:

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
于 2014-07-07T22:53:32.747 回答
7

我可能对细节有点偏离,但这里有;

来自copy文档

  • 浅拷贝构造一个新的复合对象,然后(在可能的范围内)将对原始对象中的对象的引用插入其中。
  • 深拷贝构造一个新的复合对象,然后递归地将在原始对象中找到的对象的副本插入其中。

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

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

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

于 2009-09-30T21:35:54.360 回答
3

copy模块最终使用__getstate__()/ pickling 协议,因此这些也是要覆盖的有效目标。__setstate__()

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

于 2018-03-21T16:39:40.347 回答
2

基于 Antony Hatchkins 的明确回答,这是我的版本,其中有问题的类派生自另一个自定义类(我们需要调用 st 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
于 2018-01-31T20:28:14.383 回答
1

我出于性能原因来到这里。使用默认copy.deepcopy()函数会使我的代码速度降低多达 30 倍。使用@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
于 2021-09-10T15:54:30.230 回答
1

PeterEino Gourdin的答案既聪明又有用,但它们有一个非常微妙的错误!

Python 方法绑定到它们的对象。当你这样做时cp.__deepcopy__ = deepcopy_method,你实际上是在给对象一个对原始对象cp 的引用 。任何调用都将返回原件的副本! 如果您对对象进行深度复制,然后对该副本进行深度复制,则输出不是该副本的副本!__deepcopy__ cp.__deepcopy__

这是行为的一个最小示例,以及我的固定实现,您复制__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
于 2021-07-21T00:13:29.047 回答
0

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
于 2022-02-15T11:02:32.710 回答