在Python中覆盖对象的复制/深度复制操作的正确方法是什么?

Bre*_*ode 82 python python-internals

所以只是为了建立,我觉得我理解复制模块中的copyvs 之间的区别deepcopy,我已经使用过copy.copy并且copy.deepcopy成功之前,但这是我第一次真正去重载__copy____deepcopy__方法.我已经围绕谷歌搜索,并通过看内置的Python模块查找的实例__copy____deepcopy__功能(例如sets.py,decimal.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 
Run Code Online (Sandbox Code Playgroud)

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

提前致谢!

Ant*_*ins 82

将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
Run Code Online (Sandbox Code Playgroud)

版画

init
11 [2, 3, 4, 5]
11 [2, 3, 4]
Run Code Online (Sandbox Code Playgroud)

这里__deepcopy__填写memodict以避免在对象本身从其成员引用的情况下进行过多复制.

  • @bytestorm 什么是“传输器”? (2认同)
  • @AntonyHatchkins 从你的帖子中并不能立即清楚“memo[id(self)]”实际上是用来防止无限递归的。我整理了一个[简短的例子](https://pyfiddle.io/fiddle/8352e97e-ca12-4479-afd2-05cfc431a80e/?i=true),它表明 `copy.deepcopy()` 在内部中止对如果“id()”是“memo”的键,则为一个对象,对吗?还值得注意的是,“deepcopy()”似乎_默认情况下_自行执行此操作,这使得很难想象实际需要手动定义“__deepcopy__”的情况...... (2认同)

Ale*_*lli 68

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

类可以使用相同的接口来控制用于控制酸洗的复制.有关这些方法的信息,请参阅模块pickle的说明.复制模块不使用copy_reg注册模块.

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

既然您似乎不关心酸洗定制,那么定义__copy__并且__deepcopy__绝对是正确的方式.

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

def __copy__(self):
  newone = type(self)()
  newone.__dict__.update(self.__dict__)
  return newone
Run Code Online (Sandbox Code Playgroud)

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

  • 为什么`self.foo = deepcopy(self.foo,memo)`??难道你真的不是说'newone.foo = ...`? (11认同)
  • 我认为不是类型(self)(),你应该使用cls = self .__ class__; cls .__ new __(cls)对构造函数接口不敏感(特别是对于子类化).然而,这并不重要. (9认同)
  • 这似乎不是复制/深度复制的直接翻译.复制和深度复制都不会调用要复制的对象的构造函数.考虑这个例子.class Test1(object):def __init __(self):print"%s.%s"%(self .__ class __.__ name __,"__ init__")class Test2(Test1):def __copy __(self):new = type(self) ()返回new t1 = Test1()copy.copy(t1)t2 = Test2()copy.copy(t2) (3认同)
  • @ Juh_的评论是现货.你不想打电话给`__init__`.这不是副本的作用.此外,通常还有一个用例,其中酸洗和复制需要不同.事实上,我甚至不知道为什么副本默认会尝试使用酸洗协议.复制用于内存操作,酸洗用于跨时期持久性; 它们是完全不同的东西,彼此之间几乎没有关系. (3认同)

Ein*_*din 13

遵循Peter的出色答案,实现自定义深度复制,对默认实现进行最小的更改(例如,只修改我需要的字段):

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
Run Code Online (Sandbox Code Playgroud)

  • 这是我个人的最爱,我在生产中使用它,其中一个对象有一个记录器,然后有一个线程锁,不能被腌制。保存记录器,将其设置为“无”,调用其他所有内容的默认值,然后将其放回去。面向未来,因为我不需要担心忘记处理字段,并且继承的类“可以正常工作”。 (2认同)
  • @EinoGourdin `deepcopy_method = self.__deepcopy__` 正在创建一个绑定到 `self` 的引用,然后两个对象都从类本身获取它而不是未绑定的版本。这将使从任何其他副本创建的所有副本实际上始终从原始对象创建。除非删除所有副本,否则原始对象永远不会被删除。 (2认同)

Mor*_*uhr 6

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

来自copy文档 ;

  • 浅拷贝构造一个新的复合对象,然后(尽可能)将对它的引用插入到原始对象中.
  • 深层复制构造一个新的复合对象,然后递归地将复制对象插入到原始对象中找到的对象中.

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

那就是deepcopy()你需要的.

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

  • *为了让类定义自己的副本实现,它可以定义特殊方法`__copy __(`)和`__deepcopy __()`.*http://docs.python.org/library/copy.html (2认同)

Pet*_*ter 6

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

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

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
Run Code Online (Sandbox Code Playgroud)

  • 不。如果没有找到 `__deepcopy__` 方法(或者 `obj.__deepcopy__` 返回 None),那么 `deepcopy` 会退回到标准的深度复制函数。这可以在 [here](https://github.com/python/cpython/blob/3.6/Lib/copy.py#L159) 中看到 (2认同)
  • 也许Python专家很清楚:如果您在Python 3中使用此代码,请将“ for attr, val in shared_attributes.iteritems():”更改为“ for attr, val in shared_attributes.items():” (2认同)