深拷贝与浅拷贝

Python进阶编程专题 · 理解Python的对象拷贝机制

专题:Python进阶编程系统学习

关键词:Python, 浅拷贝, 深拷贝, copy, __copy__, __deepcopy__, __getstate__, 可变性

一、概述

在Python编程中,对象的拷贝是一个看似简单却暗藏陷阱的话题。许多初学者甚至有一定经验的开发者,都曾因对拷贝机制理解不透彻而踩过坑——修改了一个列表却意外影响了另一个、函数参数修改导致了外部的副作用、复制对象时得到了意想不到的共享引用……这些问题的根源,都在于对Python对象模型和拷贝机制的理解不够深入。

Python提供三种级别的对象复制方式:赋值(绑定引用)浅拷贝深拷贝。三者的核心差异在于对嵌套对象的处理方式。赋值只是创建了一个新的引用,新旧变量指向同一对象;浅拷贝创建新对象,但只复制一层,嵌套的子对象仍共享引用;深拷贝则递归复制所有层级的对象,生成完全独立的副本。

核心概念:理解"引用"与"对象"的区别是掌握拷贝机制的基础。Python中变量名是对象的"标签"(引用),而不是存储对象的盒子。赋值操作传递的是引用,而非对象本身。

学习目标:学完本笔记后,你应当能够准确区分三种赋值方式的差异,掌握浅拷贝的三种实现方法,理解深拷贝的递归机制,能够自定义对象的拷贝行为,并能在实际项目中做出正确的拷贝选择。

二、Python中的对象引用与变量

在深入拷贝之前,必须首先理解Python的变量本质。Python中的变量不同于C或Java中的变量——它不是存储值的内存容器,而是指向对象的"名称"或"标签"。

2.1 变量即引用

Python是一门"一切皆对象"的语言。当你写下 a = [1, 2, 3] 时,实际发生的事情是:在内存中创建一个列表对象 [1, 2, 3],然后将名字 a 绑定到这个对象上。如果接着写 b = a,并不会创建新的列表,而是将名字 b 也绑定到同一个列表对象上。

import sys a = [1, 2, 3] b = a # 仅仅是绑定引用,不复制对象 print(id(a)) # 输出:140234567890123(举例) print(id(b)) # 输出:140234567890123(与a完全相同) print(a is b) # 输出:True(is 判断是否为同一对象) b.append(4) # 通过b修改列表 print(a) # 输出:[1, 2, 3, 4](a也受到影响!)

关键洞察:b = a 并没有创建副本,它只是给现有的列表对象贴了一个新标签。修改通过任何一个标签进行,另一个标签看到的是同一个被修改的对象。这就是"赋值即引用"的本质含义。

2.2 不可变对象的特殊情况

对于不可变对象(如整数、字符串、元组),Python有时会进行内部优化(如小整数缓存、字符串驻留),但这并不改变"变量即引用"的本质。不可变对象的"不可变"意味着对象本身不能被修改,但变量可以重新绑定到其他对象。

x = 42 y = x # y 绑定到 42 这个整数对象 print(x is y) # True(小整数缓存机制) x = x + 1 # 创建新整数对象 43,x 重新绑定 print(x) # 43 print(y) # 42(y 仍然指向原来的 42) print(x is y) # False(现在指向不同对象)

这里的关键是:当执行 x = x + 1 时,Python创建了新的整数对象 43,然后让 x 指向它。原来 y 指向的 42 并未受影响。这与列表的原地修改(append等)行为完全不同——列表是可变对象,可以原地改变自身内容。

三、浅拷贝(Shallow Copy)

浅拷贝创建一个新的容器对象,然后用原始对象中包含的对象的引用填充它。换句话说,浅拷贝只复制"一层"——新容器中的元素仍然是原始容器中元素的引用。如果容器中存储的是不可变对象,这通常没有影响;但如果存储的是可变对象(如嵌套列表、字典等),修改嵌套对象会同时影响原始对象和副本。

3.1 浅拷贝的三种方式

Python提供了多种实现浅拷贝的方式,适用于不同的场景。

方式一:切片操作 [:]

切片是列表最常用的浅拷贝方式。[:] 表示从头到尾的完整切片,会创建一个新的列表对象。

original = [[1, 2], [3, 4], [5, 6]] shallow = original[:] # 通过切片创建浅拷贝 print(shallow is original) # False(新的容器对象) print(shallow[0] is original[0]) # True(元素引用同一对象!) # 修改内层对象 shallow[0].append(99) print(original) # [[1, 2, 99], [3, 4], [5, 6]](原始对象也变了!) # 修改外层(重新赋值) shallow[1] = [100, 200] print(original[1]) # [3, 4](不受影响——这是重新绑定,不是修改)

方式二:.copy() 方法

Python 3.3+ 引入了 list.copy() 方法,语义与 [:] 完全相同,但可读性更好。

dict_original = {'a': [1, 2], 'b': [3, 4]} dict_shallow = dict_original.copy() # 字典的浅拷贝 dict_shallow['a'].append(5) print(dict_original) # {'a': [1, 2, 5], 'b': [3, 4]}(也被修改了) # 字典的浅拷贝也适用于 set 等集合类型 set_original = {{'x': 1}, {'y': 2}} # 不可哈希——这行只是为了说明,实际set元素必须可哈希

方式三:copy.copy() 函数

copy.copy()copy 标准库模块提供的通用浅拷贝接口。它的优势在于可以处理任何支持拷贝的对象,包括自定义类的实例。

import copy class Node: def __init__(self, value, children=None): self.value = value self.children = children or [] n1 = Node('root', [Node('child1'), Node('child2')]) n2 = copy.copy(n1) # 浅拷贝 print(n2 is n1) # False(新对象) print(n2.children is n1.children) # True(共享同一个children列表!) # 修改共享的列表 n2.children.append(Node('child3')) print(len(n1.children)) # 3(n1也被影响了!)

3.2 浅拷贝的适用场景

浅拷贝并非"不好的"拷贝方式,恰当地使用它非常高效:

# 扁平结构——浅拷贝足够安全 flat = [1, 2, 3, 'hello', (4, 5)] flat_copy = copy.copy(flat) flat_copy[0] = 99 # 替换外层元素——安全 flat_copy[3] = 'world' # 替换为新的字符串——安全(字符串不可变) print(flat) # [1, 2, 3, 'hello', (4, 5)]——不受影响

四、深拷贝(Deep Copy)

深拷贝递归地复制对象及其所有子对象,构建一个与原对象完全独立的副本。原对象和副本之间不存在任何共享引用——对副本的任何修改都不会影响原对象,反之亦然。

4.1 copy.deepcopy() 基本用法

深拷贝通过 copy.deepcopy() 函数实现。它遍历对象的所有层级,为遇到的每一个对象创建全新的副本。

import copy nested = [[1, 2, [3, 4]], {'key': [5, 6]}, (7, 8, [9, 10])] deep = copy.deepcopy(nested) print(deep is nested) # False print(deep[0] is nested[0]) # False(内层列表也是新对象) print(deep[0][2] is nested[0][2]) # False(嵌套两层仍然独立) print(deep[1] is nested[1]) # False(字典也是新对象) print(deep[2] is nested[2]) # True(元组不可变——因为要修改元组必须创建新对象) # 验证完全独立 deep[0][2].append(999) print(nested[0][2]) # [3, 4](原对象完全不受影响!)

关于元组的说明:上述代码中 deep[2] is nested[2] 的结果为 True,这并非深拷贝的缺陷。因为元组是不可变对象,如果 deepcopy 为元组创建新副本,新副本与原对象在内容上完全一致且都不可能被修改,那么创建新副本就是浪费。deepcopy 对不可变对象的优化是合理的——既然不可能被修改,共享引用也没有风险。

4.2 深拷贝的递归特性

deepcopy 的核心是递归算法。它会遍历对象的所有属性(通过 __dict____slots__),对每个属性递归调用 deepcopy,直到遇到不可变对象或基本类型为止。

递归深拷贝的简化实现可以这样理解:

def simplified_deepcopy(obj, memo=None): if memo is None: memo = {} # 1. 如果已经复制过这个对象,直接返回缓存的副本 obj_id = id(obj) if obj_id in memo: return memo[obj_id] # 2. 处理不可变类型——直接返回 if isinstance(obj, (int, float, str, bytes)): return obj # 3. 处理容器类型——递归复制每个元素 if isinstance(obj, list): new = [] memo[obj_id] = new # 先注册,防止自引用无限递归 for item in obj: new.append(simplified_deepcopy(item, memo)) return new if isinstance(obj, dict): new = {} memo[obj_id] = new for k, v in obj.items(): new[simplified_deepcopy(k, memo)] = simplified_deepcopy(v, memo) return new # 4. 处理自定义对象——创建空实例后递归填充属性 if hasattr(obj, '__dict__'): cls = type(obj) new = cls.__new__(cls) memo[obj_id] = new for attr, val in obj.__dict__.items(): setattr(new, attr, simplified_deepcopy(val, memo)) return new return obj # 兜底(实际copy.deepcopy远此复杂)

注意上述代码是教学演示,真实的 copy.deepcopy 实现要复杂得多——它需要处理 __slots____getstate__/__setstate__、模块类型、文件对象、具有特殊构造函数的类型等众多边界情况。

4.3 共享引用问题的处理

当对象结构中存在共享引用时,深拷贝能否正确保持这种共享关系?答案是:默认情况下,deepcopy 会在新副本中"打破"共享关系,为每个对象创建独立的副本。如果需要在副本中保持同样的共享模式,需要自定义拷贝行为。

import copy # 创建一个有共享引用的结构 shared = ['shared'] obj = [shared, shared, shared] # 同一个对象被引用了三次 print(obj[0] is obj[1]) # True(共享引用) obj_deep = copy.deepcopy(obj) print(obj_deep[0] is obj_deep[1]) # True(共享引用被保持了!) print(obj_deep[0] is shared) # False(但新副本与原对象无关) # 这是因为 deepcopy 的 memo 机制记住了已经复制过的对象 obj_deep[0].append('new') print(obj_deep[1]) # ['shared', 'new'](共享关系被保持!三个引用都受影响) print(obj[0]) # ['shared'](原对象不受影响)

4.4 无限递归与自引用结构

如果对象中存在循环引用(自引用),天真地递归复制会导致无限递归和栈溢出。deepcopy 通过 memo 字典(备忘录)优雅地解决了这个问题:在复制一个对象之前,先检查它是否已被复制过,如果是,则返回已缓存的副本。

风险提示:使用自定义 __deepcopy__ 方法时,如果忘记传递 memo 参数或未正确维护 memo,很容易导致无限递归。务必确保你的自定义实现正确处理 memo 字典。

import copy # 创建一个自引用的列表(循环引用) cyclic = [1, 2, 3] cyclic.append(cyclic) # 自己引用自己 print(cyclic) # [1, 2, 3, [...]] print(cyclic[3] is cyclic) # True # 深拷贝自引用结构——没有栈溢出! cyclic_deep = copy.deepcopy(cyclic) print(cyclic_deep) # [1, 2, 3, [...]] print(cyclic_deep[3] is cyclic_deep) # True(自引用关系保持) print(cyclic_deep is cyclic) # False(副本与原对象独立)

五、可变与不可变对象的拷贝行为差异

Python中对象分为可变(mutable)和不可变(immutable)两大类。可变对象包括列表、字典、集合以及自定义类的实例;不可变对象包括整数、浮点数、字符串、元组、frozenset等。这一区别对拷贝行为有深远影响。

5.1 不可变对象的"假拷贝"

当你对不可变对象执行浅拷贝或深拷贝时,Python不会创建新对象,而是返回原对象的引用。这是因为不可变对象的值永远不会改变,共享引用不会产生任何安全隐患。

import copy t = (1, 2, 3) t_copy1 = copy.copy(t) t_copy2 = copy.deepcopy(t) print(t_copy1 is t) # True(copy.copy 对不可变对象直接返回原对象) print(t_copy2 is t) # True(copy.deepcopy 也一样) # 但如果元组中包含可变对象,情况就不同了 t_mixed = (1, [2, 3], {'a': 4}) t_shallow = copy.copy(t_mixed) t_deep = copy.deepcopy(t_mixed) print(t_shallow is t_mixed) # True(元组本身还是同一个) print(t_shallow[1] is t_mixed[1]) # True(浅拷贝,列表共享) print(t_deep[1] is t_mixed[1]) # False(深拷贝,列表是新对象)

5.2 可变对象的拷贝行为

可变对象的浅拷贝和深拷贝都会创建新对象。区别在于子对象的处理方式。

浅拷贝(新建顶层容器)

new_object is not original

new_object[0] is original[0] (嵌套对象共享)

深拷贝(完全独立)

new_object is not original

new_object[0] is not original[0] (嵌套对象也复制)

5.3 完整对比表

操作 顶层对象 嵌套可变对象 嵌套不可变对象
b = a 共享(同一对象) 共享 共享
b = copy.copy(a) 新对象 共享 共享(无影响)
b = copy.deepcopy(a) 新对象 新对象 共享(无影响)

六、__copy__ 与 __deepcopy__:自定义拷贝行为

Python 的 copy 模块提供了扩展点,允许自定义类控制自己的拷贝行为。通过定义 __copy____deepcopy__ 方法,你可以精确指定类实例被浅拷贝和深拷贝时应当如何进行。

6.1 __copy__ 方法

__copy__ 方法不接受额外参数(除了 self),返回浅拷贝的结果。当你实现这个方法时,copy.copy() 会调用它而不是使用默认行为。

import copy class DatabaseConnection: def __init__(self, host, port, cache=None): self.host = host self.port = port self.cache = cache or {} def __copy__(self): """浅拷贝:共享缓存数据,但创建一个新的连接配置对象""" print("调用 __copy__ 方法") # 注意:新对象共享了缓存(浅拷贝的典型行为) return DatabaseConnection(self.host, self.port, self.cache) def __repr__(self): return f"DBConnection(host={self.host}, port={self.port})" db1 = DatabaseConnection('localhost', 5432) db1.cache['users'] = ['alice', 'bob'] db2 = copy.copy(db1) print(db2.cache is db1.cache) # True(共享缓存) # 修改缓存会影响所有浅拷贝 db2.cache['new_key'] = 'value' print('new_key' in db1.cache) # True

6.2 __deepcopy__ 方法

__deepcopy__ 方法接受一个 memo 参数(字典),必须将新创建的副本注册到 memo 中,以防止无限递归并保持共享引用关系。

import copy class TreeNode: def __init__(self, value): self.value = value self.left = None self.right = None self.parent = None # 指向父节点(可能形成循环引用) def __deepcopy__(self, memo): """深拷贝:递归复制整棵子树,并正确处理 parent 引用""" print(f"deepcopy: {self.value}") # 1. 检查 memo,避免重复复制和循环引用 if id(self) in memo: return memo[id(self)] # 2. 创建新实例(不调用 __init__) new_node = TreeNode.__new__(TreeNode) memo[id(self)] = new_node # 必须先注册! # 3. 递归复制属性 new_node.value = copy.deepcopy(self.value, memo) new_node.left = copy.deepcopy(self.left, memo) # 递归复制左子树 new_node.right = copy.deepcopy(self.right, memo) # 递归复制右子树 new_node.parent = copy.deepcopy(self.parent, memo) # 正确关联父节点 return new_node # 构建树结构 root = TreeNode('root') left = TreeNode('left') right = TreeNode('right') root.left = left root.right = right left.parent = root right.parent = root # 深拷贝 root_copy = copy.deepcopy(root) print(root_copy is root) # False(不同对象) print(root_copy.left.parent is root_copy) # True(parent 指向正确的副本) print(root_copy.right.parent is root_copy) # True(parent 指向正确的副本)

6.3 自定义拷贝的最佳实践

七、deepcopy 的 memo 参数详解

memodeepcopy 内部使用的"备忘录"字典,它在深拷贝过程中承担两个关键职责:

  1. 避免重复复制:当同一个对象被多次引用时,memo 确保它只被复制一次,后续引用直接返回已创建的副本
  2. 打破循环引用:对于自引用结构(如双向链表、树结构的 parent 指针),memo 防止无限递归

7.1 memo 的工作原理

memo 是一个以 id(original) 为键、以副本对象为值的映射表。在 deepcopy 的递归过程中:

import copy # 观察 memo 的工作过程 class WatchedObject: def __init__(self, name): self.name = name self.ref = None def __deepcopy__(self, memo): # 检查 memo:如果已经复制过,立刻返回 if id(self) in memo: print(f" 命中缓存: {self.name}") return memo[id(self)] print(f" 开始复制: {self.name}") new = WatchedObject.__new__(WatchedObject) memo[id(self)] = new # 立即注册 new.name = copy.deepcopy(self.name, memo) new.ref = copy.deepcopy(self.ref, memo) # 递归处理引用 print(f" 完成复制: {self.name}") return new def __repr__(self): return f"Watched({self.name})" a = WatchedObject('A') b = WatchedObject('B') a.ref = b b.ref = a # 形成循环引用 print("开始深拷贝...") a_copy = copy.deepcopy(a) print("深拷贝完成") print(a_copy.ref.ref is a_copy) # True(循环引用正确保持)

7.2 外部传入 memo

你可以在调用 deepcopy 时传入一个已有的 memo 字典。这在需要跨多个独立对象保持共享引用关系时非常有用。

import copy class SharedData: def __init__(self, data): self.data = data shared_resource = SharedData([1, 2, 3]) # 两个对象共享同一个 resource container1 = [shared_resource, 'first'] container2 = [shared_resource, 'second'] # 分别深拷贝——每个容器中的 shared_resource 被复制成独立对象 c1_independent = copy.deepcopy(container1) c2_independent = copy.deepcopy(container2) print(c1_independent[0] is c2_independent[0]) # False(两份独立副本) # 使用同一个 memo 深拷贝——共享关系被保持 memo = {} c1_shared = copy.deepcopy(container1, memo) c2_shared = copy.deepcopy(container2, memo) print(c1_shared[0] is c2_shared[0]) # True(共享同一个副本!)

应用场景:当需要深拷贝一个复杂对象图(object graph),且图中存在跨对象的共享引用时,使用同一个 memo 字典可以确保共享关系在新副本中得以保持。这在序列化/反序列化、缓存复制、模型克隆等场景中非常有用。

八、__getstate__ 与 __setstate__ 在深拷贝中的作用

__getstate____setstate__ 是 Python 中与对象序列化相关的两个特殊方法,它们在 pickle 模块中广泛使用。有趣的是,copy.deepcopy 也会检查并利用这两个方法,因为它们提供了一种精确控制对象状态提取和恢复的机制。

8.1 基本机制

当 deepcopy 遇到一个定义了 __getstate__ 方法的对象时,会调用该方法获取对象的"可复制状态"。然后创建一个空实例(不调用 __init__),再调用 __setstate__ 将状态恢复到新对象中。

import copy class SecureDocument: def __init__(self, content, password): self.content = content self._password = password # 敏感信息,不应出现在副本中 self._cache = {} # 缓存数据,复制后应清空 self._loaded = True def __getstate__(self): """控制哪些状态被复制到深拷贝中""" print("调用 __getstate__") # 返回一个 dict,包含需要复制的状态 return { 'content': self.content, # 故意不包含 _password 和 _cache '_loaded': False, # 重置加载状态 } def __setstate__(self, state): """从深拷贝恢复状态""" print("调用 __setstate__") self.content = state['content'] self._password = None # 安全地设为 None self._cache = {} # 空缓存 self._loaded = state.get('_loaded', False) def __repr__(self): return f"SecureDocument(content={self.content!r}, has_pwd={self._password is not None})" doc = SecureDocument("机密数据", "secret123") doc._cache['计算结果'] = 42 doc_copy = copy.deepcopy(doc) print(doc_copy) print(doc_copy._password) # None(密码未被复制) print(doc_copy._cache) # {}(缓存被清空) print(doc_copy._loaded) # False(状态被重置)

8.2 deepcopy 与 pickle 的关系

copy.deepcopypickle 都使用 __getstate__/__setstate__,但目的不同:

特性 copy.deepcopy pickle
主要用途 创建对象的在内存中的副本 对象的序列化/反序列化(存储或传输)
调用 __reduce__ 否(有单独的复制协议) 是(主要序列化协议)
__getstate__ 支持 是(deepcopy 会检查) 是(pickle 的核心协议)
__setstate__ 支持
__copy__ 支持 是(浅拷贝协议)
__deepcopy__ 支持 是(深拷贝协议)
memo 参数 是(核心机制) 否(使用 pickler 内部缓存)

8.3 deepcopy 的内部决策流程

当 deepcopy 处理一个对象时,它会按以下优先级选择复制方式:

  1. 检查 memo:如果对象已被复制,返回缓存的副本
  2. 检查 __deepcopy__:如果定义了,委托给该方法
  3. 检查 __getstate__:如果定义了,使用状态提取+恢复的方式复制
  4. 默认行为:遍历 __dict__ 或 __slots__ 递归复制每个属性

设计建议:如果你需要在深拷贝时精确控制哪些属性被复制、哪些被排除(如缓存、连接池、密码等),定义 __getstate__ 是比 __deepcopy__ 更简洁的方案。但如果你需要完全控制复制过程的每一步(如处理循环引用、优雅地处理不可复制资源),则应当使用 __deepcopy__

九、实战应用与常见陷阱

9.1 函数参数的拷贝陷阱

将可变对象作为函数默认参数时,如果不注意拷贝问题,可能导致难以追踪的 bug。

错误示范(共享引用)

def add_item(item, target=[]): # 危险! target.append(item) return target print(add_item(1)) # [1] print(add_item(2)) # [1, 2] —— 污染了! print(add_item(3)) # [1, 2, 3] —— 不断累积

正确做法(浅拷贝)

def add_item(item, target=None): if target is None: target = [] # 每次调用创建新列表 target.append(item) return target print(add_item(1)) # [1] print(add_item(2)) # [2] —— 干净!

9.2 多维列表初始化

这是一个经典陷阱,源于对浅拷贝机制理解不足。

错误示范

# [[0]*3]*3 创建了三个对同一行的引用! matrix = [[0] * 3] * 3 matrix[0][0] = 1 print(matrix) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]] # 所有"行"都变了!

正确做法

# 使用列表推导式,每行单独创建 matrix = [[0] * 3 for _ in range(3)] matrix[0][0] = 1 print(matrix) # [[1, 0, 0], [0, 0, 0], [0, 0, 0]] # 只有第一行被修改

9.3 不可复制对象的处理

某些对象(如文件句柄、网络连接、线程锁)本质上不支持复制。深拷贝它们会引发 TypeError

import copy import threading lock = threading.Lock() try: lock_copy = copy.deepcopy(lock) except TypeError as e: print(f"无法复制锁对象:{e}") # 解决方案:自定义 __deepcopy__ 或 __getstate__ class SafeLock: def __init__(self): self._lock = threading.Lock() self._owner = None def __deepcopy__(self, memo): # 创建新实例,锁对象不被复制 new_obj = SafeLock.__new__(SafeLock) memo[id(self)] = new_obj new_obj._lock = threading.Lock() # 创建新的锁 new_obj._owner = copy.deepcopy(self._owner, memo) return new_obj safe = SafeLock() safe_copy = copy.deepcopy(safe) # 正常工作 print(safe_copy._lock is safe._lock) # False

9.4 性能考量

深拷贝的性能开销远高于浅拷贝。对于大型复杂对象图,深拷贝可能消耗大量时间和内存。

import copy import time # 模拟一个大型数据结构 big_data = {'level1': [{'level2': ['x' * 1000 for _ in range(100)]} for _ in range(1000)} t0 = time.time() shallow = copy.copy(big_data) t1 = time.time() deep = copy.deepcopy(big_data) t2 = time.time() print(f"浅拷贝耗时:{t1 - t0:.4f}秒") print(f"深拷贝耗时:{t2 - t1:.4f}秒") print(f"深度比:{(t2 - t1) / (t1 - t0):.1f}x") # 深拷贝通常比浅拷贝慢数十到数百倍

性能建议:

  • 能用浅拷贝就不要用深拷贝
  • 如需多次深拷贝同一结构,考虑先 pickle 序列化再反序列化(对某些结构更快)
  • 对于游戏对象、物理模拟等性能敏感场景,手动编写专门的复制方法通常比通用 deepcopy 更高效
  • 使用 __deepcopy__ 实现自定义复制可以跳过不必要的复制,显著提升性能

十、核心要点总结

1. 变量即引用:Python 变量是对象的标签,赋值操作传播的是引用而非对象本身。b = a 不创建副本。

2. 浅拷贝复制一层:[:].copy()copy.copy() 都创建新容器,但嵌套的子对象仍共享引用。适合扁平结构或明确需要共享的场景。

3. 深拷贝递归复制:copy.deepcopy() 递归遍历所有层级,为每个可变对象创建独立副本,完全的隔离保护。

4. memo 是深拷贝的基石:通过备忘录字典避免重复复制和无限递归,同一个对象只被复制一次,共享引用关系得以保持。

5. 不可变对象不会被复制:整数、字符串、元组等不可变对象在浅拷贝和深拷贝中都返回原引用,这是安全的优化。

6. __copy__ 和 __deepcopy__ 自定义拷贝:定义这两个方法可以精确控制类的拷贝行为,处理特殊逻辑(如排除密码、重置缓存、处理不可复制资源)。

7. __getstate__ 和 __setstate__:deepcopy 利用这两个方法进行状态提取和恢复,提供了一种简洁的定制拷贝方式。

8. 注意默认参数和多维列表陷阱:函数默认参数使用可变对象、[[0]*3]*3 都是浅拷贝陷阱的高发区。

9. 性能权衡:深拷贝比浅拷贝慢数十到数百倍,应根据实际需求选择最合适的复制策略。

10. 选择指南:赋值(仅需要额外引用) -> 浅拷贝(扁平结构或明确共享) -> 深拷贝(需要完全隔离、对象图复杂多变)。

十一、进一步思考

思考一:为什么 Python 的默认参数在函数定义时(而非调用时)求值?这与拷贝机制有何关系?

思考二:在单例模式中,__copy____deepcopy__ 应该如何实现?是否应该禁止拷贝单例对象?

思考三:Python 的 dataclasses.dataclass 装饰器生成的类,其 __copy____deepcopy__ 行为是怎样的?你能否写一个 dataclass 的混入类,使得它的深拷贝默认排除某些字段?

思考四:如果你需要实现一个"部分深拷贝"——复制对象的某些子对象但共享其他子对象——应该如何设计 API?