3.10. copy — 对象复制

未匹配的标注

目的:用浅复制或深复制的语义来为对象提供复制函数。

copy 模块为复制已存在的对象提供两个方法, copy()deepcopy()

浅拷贝


浅复制 是用 copy() 来创建的一个填充了对原始对象引用的新容器。当创建一个 list 对象的浅复制的时候,会构造一个新的 list 并将原始对象的元素附加到它上面。

copy_shallow.py

import copy
import functools

@functools.total_ordering
class MyClass:

    def __init__(self, name):
        self.name = name

    def __eq__(self, other):
        return self.name == other.name

    def __gt__(self, other):
        return self.name > other.name

a = MyClass('a')
my_list = [a]
dup = copy.copy(my_list)

print('             my_list:', my_list)
print('                 dup:', dup)
print('      dup is my_list:', (dup is my_list))
print('      dup == my_list:', (dup == my_list))
print('dup[0] is my_list[0]:', (dup[0] is my_list[0]))
print('dup[0] == my_list[0]:', (dup[0] == my_list[0]))

对于浅复制, MyClass 实例并没有被复制, 所以在 dup 列表和 my_list 列表中引用的其实是同一个对象。

$ python3 copy_shallow.py

             my_list: [<__main__.MyClass object at 0x101f9c160>]
                 dup: [<__main__.MyClass object at 0x101f9c160>]
      dup is my_list: False
      dup == my_list: True
dup[0] is my_list[0]: True
dup[0] == my_list[0]: True

深拷贝

deepcopy() 创建的 深拷贝 是一个新的容器, 容器里面填充了原始对象内容的副本。为了创建一个 list 的深拷贝,会先创建一个新的 list 对象, 复制原序列里面的元素,然后将这些副本添加到新的序列当中。

deepcopy() 代替 copy() 进行调用,通过输出观察两者的差异。

copy_deep.py

import copy
import functools

@functools.total_ordering
class MyClass:

    def __init__(self, name):
        self.name = name

    def __eq__(self, other):
        return self.name == other.name

    def __gt__(self, other):
        return self.name > other.name

a = MyClass('a')
my_list = [a]
dup = copy.deepcopy(my_list)

print('             my_list:', my_list)
print('                 dup:', dup)
print('      dup is my_list:', (dup is my_list))
print('      dup == my_list:', (dup == my_list))
print('dup[0] is my_list[0]:', (dup[0] is my_list[0]))
print('dup[0] == my_list[0]:', (dup[0] == my_list[0]))

序列中的第一个对象的引用不再是原序列中第一个对象的引用, 但是当两个对象进行比较的时候, 他们仍然被认为是相同的。

$ python3 copy_deep.py

             my_list: [<__main__.MyClass object at 0x101e9c160>]
                 dup: [<__main__.MyClass object at 0x1044e1f98>]
      dup is my_list: False
      dup == my_list: True
dup[0] is my_list[0]: False
dup[0] == my_list[0]: True

自定义复制行为

通过 __copy__()__deepcopy__() 这两个特殊方法可以控制复制行为。

  • 调用 __copy__() 方法的无需带任何参数,返回对象的浅拷贝。
  • __deepcopy__() 被备忘字典调用并且返回对象的深拷贝。任何需要深度复制的成员属性都应该和备忘字典一起传递给 copy.deepcopy() 去控制递归。(备忘字典将在后面详细介绍。)

以下示例说明如何调用这些方法。

copy_hooks.py

import copy
import functools

@functools.total_ordering
class MyClass:

    def __init__(self, name):
        self.name = name

    def __eq__(self, other):
        return self.name == other.name

    def __gt__(self, other):
        return self.name > other.name

    def __copy__(self):
        print('__copy__()')
        return MyClass(self.name)

    def __deepcopy__(self, memo):
        print('__deepcopy__({})'.format(memo))
        return MyClass(copy.deepcopy(self.name, memo))

a = MyClass('a')

sc = copy.copy(a)
dc = copy.deepcopy(a)

备忘字典通过追踪已经被复制的值来避免无限递归。

$ python3 copy_hooks.py

__copy__()
__deepcopy__({})

深拷贝中的递归

为了避免重复的递归数据结构, deepcopy() 用了一个字典跟踪被拷贝的对象。这个字典会被传递给 __deepcopy__() 方法,它将在这个方法内被检查。

下面的例子通过实现了 __deepcopy__() 方法展示了如何使用像有向图一样的互联数据结构防止递归的。

copy_recursion.py

import copy

class Graph:

    def __init__(self, name, connections):
        self.name = name
        self.connections = connections

    def add_connection(self, other):
        self.connections.append(other)

    def __repr__(self):
        return 'Graph(name={}, id={})'.format(
            self.name, id(self))

    def __deepcopy__(self, memo):
        print('\nCalling __deepcopy__ for {!r}'.format(self))
        if self in memo:
            existing = memo.get(self)
            print('  Already copied to {!r}'.format(existing))
            return existing
        print('  Memo dictionary:')
        if memo:
            for k, v in memo.items():
                print('    {}: {}'.format(k, v))
        else:
            print('    (empty)')
        dup = Graph(copy.deepcopy(self.name, memo), [])
        print('  Copying to new object {}'.format(dup))
        memo[self] = dup
        for c in self.connections:
            dup.add_connection(copy.deepcopy(c, memo))
        return dup

root = Graph('root', [])
a = Graph('a', [root])
b = Graph('b', [a, root])
root.add_connection(a)
root.add_connection(b)

dup = copy.deepcopy(root)

Graph 类包含了几种基本的有向图方法。一个实例可以被一个名称和它所连接的现有节点的列表初始化。add_connection() 方法用于设置双向连接。他也被深拷贝操作符所使用。

__deepcopy__() 方法通过打印信息来展现它是如何被调用的, 并且根据需要管理备忘录字典的内容。 他不是批量的复制整个连接列表, 而是创建一个新的列表并且将单个连接的副本附加到它。这个可以确保备忘录字典会随着每个新节点的复制而更新,避免了递归问题或节点的额外副本, 该方法在完成时返回复制的对象。

digraph copy_example { "root"; "a" -> "root"; "b" -> "root"; "b" -> "a"; "root" -> "a"; "root" -> "b"; }

具有循环的对象图的深拷贝

图中的图形包含着几个循环, 但使用备忘录字典可以防止遍历导致的堆栈溢出错误。当 root 节点被复制的时候,它会产生以下输出。

$ python3 copy_recursion.py

Calling __deepcopy__ for Graph(name=root, id=4326183824)
  Memo dictionary:
    (empty)
  Copying to new object Graph(name=root, id=4367233208)

Calling __deepcopy__ for Graph(name=a, id=4326186344)
  Memo dictionary:
    Graph(name=root, id=4326183824): Graph(name=root,
id=4367233208)
  Copying to new object Graph(name=a, id=4367234720)

Calling __deepcopy__ for Graph(name=root, id=4326183824)
  Already copied to Graph(name=root, id=4367233208)

Calling __deepcopy__ for Graph(name=b, id=4326183880)
  Memo dictionary:
    Graph(name=root, id=4326183824): Graph(name=root,
id=4367233208)
    Graph(name=a, id=4326186344): Graph(name=a, id=4367234720)
    4326183824: Graph(name=root, id=4367233208)
    4367217936: [Graph(name=root, id=4326183824), Graph(name=a,
id=4326186344)]
    4326186344: Graph(name=a, id=4367234720)
  Copying to new object Graph(name=b, id=4367235000)

第二次遇到 root 节点的时候,并且 a 节点被复制的时候, __deepcopy__() 检测到递归并且重用备忘录字典中已经存在的值,而不是创建新对象。

参考

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/pymotw/copy-obj...

译文地址:https://learnku.com/docs/pymotw/copy-obj...

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~