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

时间:2009-09-30 21:18:46

标签: python

我了解复制模块中copydeepcopy之间的区别。我在成功之前使用了copy.copycopy.deepcopy,但这是我第一次真正重载__copy____deepcopy__方法。我已经用Google搜索并查看了内置的Python模块,以查找__copy____deepcopy__函数的实例(例如sets.pydecimal.py和{{ 1}}),但我仍然不能100%确定我做对了。

这是我的情景:

我有一个配置对象。最初,我将使用一组默认值来实例化一个配置对象。此配置将切换到多个其他对象(以确保所有对象以相同的配置启动)。但是,一旦用户交互开始,每个对象都需要独立调整其配置,而不会影响彼此的配置(对我来说,我需要使用我的初始配置的深度复制来处理)。

这是一个示例对象:

fractions.py

在此对象上实施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 copy方法的正确方法是什么,以确保deepcopycopy.copy给我正确的行为?

8 个答案:

答案 0 :(得分:74)

将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 dict以避免在对象本身从其成员引用的情况下进行过多复制。

答案 1 :(得分:62)

自定义建议位于docs page

的最后
  

类可以使用相同的接口   控制他们使用的复制   控制酸洗。请参阅说明   模块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(实质上属性是容器 - 列表,dicts,非原始对象,通过__dict__来保存其他东西。

答案 2 :(得分:10)

关注Peter's excellent answer,实现自定义深度复制,对默认实现进行最少的更改(例如只修改我需要的字段):

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

        # custom treatments
        # for instance: cp.id = None

        return cp

答案 3 :(得分:5)

我可能会对细节有所了解,但是这里可以了;

来自copy docs;

  
      
  • 浅复制构造一个新的复合对象,然后(尽可能)将对它的引用插入到原始对象中找到的对象。
  •   
  • 深层复制构造一个新的复合对象,然后以递归方式将副本插入到原始对象中找到的对象。
  •   

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

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

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

答案 4 :(得分:5)

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

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

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

答案 5 :(得分:1)

copy模块逐渐使用__getstate__() / __setstate__() 腌制协议,因此这些也是有效的覆盖目标。

默认实现只返回并设置班级的__dict__,因此您不必致电super()并担心Eino Gourdin的聪明伎俩,{{3 }}

答案 6 :(得分: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

答案 7 :(得分:0)

在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