Python的`copy`模块
本文解释了Python的copy模块。
聚焦浅拷贝与深拷贝的差异,我们从对象复制的基本机制到在自定义类中的应用进行清晰讲解,并配以实用示例。
YouTube Video
Python的copy模块
Python 的 copy 模块是处理对象复制(拷贝)的标准模块。目标是理解浅拷贝与深拷贝的区别,并能够控制自定义对象的拷贝行为。
基础:什么是浅拷贝?
这里以列表、字典等可变对象为例说明浅拷贝的行为。浅拷贝只复制最外层对象,内部元素的引用会被共享。
1# Example: shallow copy with lists and dicts
2import copy
3
4# A nested list
5original = [1, [2, 3], 4]
6shallow = copy.copy(original)
7
8# Mutate nested element
9shallow[1].append(99)
10
11print("original:", original) # nested change visible in original
12print("shallow: ", shallow)在这段代码中,顶层列表被复制,但内部的子列表通过引用共享,因此 shallow[1].append(99) 也会在 original 中体现出来。这正是浅拷贝的典型行为。
什么是深拷贝?
深拷贝会递归地复制对象及其所有内部引用,生成彼此独立的对象。当需要安全地复制复杂的嵌套结构时应使用它。
1# Example: deep copy with nested structures
2import copy
3
4original = [1, [2, 3], {'a': [4, 5]}]
5deep = copy.deepcopy(original)
6
7# Mutate nested element of the deep copy
8deep[1].append(100)
9deep[2]['a'].append(200)
10
11print("original:", original) # original remains unchanged
12print("deep: ", deep)在这个示例中,对 deep 的修改不会影响 original。deepcopy 会复制整个对象图,得到一个独立的副本。
如何选择使用哪一种
应根据对象的结构和你的目的来决定使用哪种拷贝。如果不希望对内部可变元素的更改影响原对象,应使用 deepcopy。这是因为它会递归复制整个对象,生成完全独立的副本。
另一方面,如果只复制最外层对象就足够,并且更看重速度和内存效率,那么 copy(浅拷贝)更合适。
- 希望在修改内部可变元素时避免原对象发生变化 → 使用
deepcopy。 - 只需复制最外层并希望优先考虑性能(速度/内存) → 使用
copy(浅拷贝)。
内置与不可变对象的处理
不可变对象(例如 int、str 以及内容不可变的元组)通常不需要复制。copy.copy 在合适的情况下可能返回同一个对象。
1# Example: copying immutables
2import copy
3
4a = 42
5b = copy.copy(a)
6print(a is b) # True for small ints (implementation detail)
7
8s = "hello"
9t = copy.deepcopy(s)
10print(s is t) # True (strings are immutable)由于复制不可变对象几乎没有效率收益,可能会直接复用同一个对象。在应用设计中,这几乎不需要担心。
自定义类:定义 __copy__ 与 __deepcopy__
如果默认拷贝行为不符合你的类的预期,可以提供自定义的拷贝逻辑。如果定义了 __copy__ 和 __deepcopy__,copy.copy 与 copy.deepcopy 将会使用它们。
1# Example: customizing copy behavior
2import copy
3
4class Node:
5 def __init__(self, value, children=None):
6 self.value = value
7 self.children = children or []
8
9 def __repr__(self):
10 return f"Node({self.value!r}, children={self.children!r})"
11
12 def __copy__(self):
13 # Shallow copy: create new Node, but reuse children list reference
14 new = self.__class__(self.value, self.children)
15 return new
16
17 def __deepcopy__(self, memo):
18 # Deep copy: copy value and deepcopy each child
19 new_children = [copy.deepcopy(child, memo) for child in self.children]
20 new = self.__class__(copy.deepcopy(self.value, memo), new_children)
21 memo[id(self)] = new
22 return new- 通过实现
__copy__和__deepcopy__,你可以灵活地控制类特有的复制行为。例如,你可以处理某些属性应当引用(共享)同一个对象,而其他属性应当被复制为全新的对象的情况。 - 传递给
__deepcopy__的memo参数是一个字典,用于在递归复制过程中记录已处理过的对象,并防止由循环引用导致的无限循环。
1# Build tree
2root_node = Node('root', [Node('child1'), Node('child2')])
3
4shallow_copy = copy.copy(root_node) # shallow copy
5deep_copy = copy.deepcopy(root_node) # deep copy
6
7# Modify children
8# affects both root_node and shallow_copy
9shallow_copy.children.append(Node('child_shallow'))
10# affects deep_copy only
11deep_copy.children.append(Node('child_deep'))
12
13# Print results
14print("root_node:", root_node)
15print("shallow_copy:", shallow_copy)
16print("deep_copy:", deep_copy)- 在这段代码中,变量
shallow_copy是通过'浅拷贝'创建的,因此只复制了对象的顶层,其内部引用的对象(在此例中为children列表)与原始的root_node共享。因此,当你向shallow_copy添加新节点时,共享的children列表会被更新,这一变化也会反映在root_node的内容中。 - 另一方面,变量
deep_copy是通过'深拷贝'创建的,因此内部结构会被递归地复制,生成一棵与root_node完全独立的树。因此,即使你向deep_copy添加一个新节点,也不会影响root_node。
循环引用(递归对象)与 memo 的重要性
当复杂对象图出现自我引用(循环引用)时,deepcopy 使用一个 memo 字典来跟踪已复制的对象,以防止无限循环。
1# Example: recursive list and deepcopy memo demonstration
2import copy
3
4a = []
5b = [a]
6a.append(b) # a -> [b], b -> [a] (cycle)
7
8# deepcopy can handle cycles
9deep = copy.deepcopy(a)
10print("deep copy succeeded, length:", len(deep))- 在内部,
deepcopy通过memo引用已复制的对象,即便存在循环也能安全地完成复制。在手动实现递归复制时,你也需要类似的机制。
copyreg 与序列化风格的自定义拷贝(进阶)
如果希望与库协同注册特殊的拷贝行为,可以使用标准模块 copyreg 来注册对象的(重)构方式。这对复杂对象和 C 扩展类型很有用。
1# Example: using copyreg to customize pickling/copy behavior (brief example)
2import copy
3import copyreg
4
5class Wrapper:
6 def __init__(self, data):
7 self.data = data
8
9def reduce_wrapper(obj):
10 # Return callable and args so object can be reconstructed
11 return (Wrapper, (obj.data,))
12
13copyreg.pickle(Wrapper, reduce_wrapper)
14
15w = Wrapper([1, 2, 3])
16w_copy = copy.deepcopy(w)
17print("w_copy.data:", w_copy.data)- 使用
copyreg,你可以提供同时影响pickle与deepcopy的重建规则。这是一套进阶 API,在大多数情况下仅实现__deepcopy__就足够了。
实践中的注意事项与陷阱
为确保使用 copy 模块时的正确行为,有几个重要要点需要注意。下面说明开发中常见的坑以及如何避免。
- 性能
deepcopy可能消耗大量内存和 CPU,因此要仔细评估它是否必要。 - 处理希望共享的元素
如果希望某些属性(如大型缓存)保持共享,可在
__deepcopy__中有意避免复制它们的引用。 - 不可变的内部状态 如果内部持有的是不可变数据,可能无需复制。
- 线程与外部资源 无法复制的资源(如套接字、文件句柄)要么复制没有意义,要么会导致错误,因此在设计上必须避免对它们进行复制。
实践示例:安全更新字典的模式
在更新复杂的配置对象时,本示例使用 deepcopy 来保护原始设置。
1# Example: safely update a nested configuration using deepcopy
2import copy
3
4default_config = {
5 "db": {"host": "localhost", "ports": [5432]},
6 "features": {"use_cache": True, "cache_sizes": [128, 256]},
7}
8
9# Create a working copy to modify without touching defaults
10working = copy.deepcopy(default_config)
11working["db"]["ports"].append(5433)
12working["features"]["cache_sizes"][0] = 512
13
14print("default_config:", default_config)
15print("working:", working)通过 deepcopy,可以安全地创建派生配置,而不必担心破坏默认设置。这对配置等嵌套的可变结构尤其有用。
最佳实践
要安全且高效地使用 copy 模块,务必牢记以下实用准则。
- 根据是否会发生更改及其影响范围来选择浅拷贝或深拷贝。
- 在自定义类中实现
__copy__与__deepcopy__,以获得预期的行为。 - 由于深拷贝代价高,应尽量通过设计降低对拷贝的需求。可考虑使用不可变设计、显式的克隆方法等技术。
- 在处理循环引用时,要么利用
deepcopy,要么手动提供类似 memo 的机制。 - 在设计时应确保诸如文件句柄和线程等外部资源不会被复制。
结论
copy 模块是 Python 中用于对象复制的标准工具。正确理解浅拷贝与深拷贝的差异,并在需要时实现自定义拷贝行为,可以让复制过程安全且可预测。在设计阶段明确是否真的需要复制、哪些内容应被共享,可以避免不必要的缺陷与性能问题。
您可以在我们的YouTube频道上使用Visual Studio Code跟随上述文章进行学习。 请也查看我们的YouTube频道。