直接用 copy.deepcopy() 不够用,因其无法区分字段复制策略:需深拷的业务数据、需共享的单例资源、需重置的运行时状态(如id、锁)及不可序列化对象(如socket)均会出错;应通过 deepcopy 手动控制各字段行为,或用原型工厂封装不同克隆策略。

为什么直接用 copy.deepcopy() 不够用?
原型模式的核心不是“能复制”,而是“可控地复用已有结构”。copy.deepcopy() 确实能克隆嵌套对象,但它不区分哪些字段该深拷贝、哪些该共享(比如连接池、配置单例)、哪些该重置(如 ID、时间戳)。一旦对象树里混着 threading.Lock、socket.socket 或自定义的不可序列化资源,deepcopy 就会抛 TypeError: cannot pickle 'xxx' object。
常见错误现象:
- 复制后两个实例意外共享了某个缓存字典,改一个影响另一个
- 克隆后的对象仍持有原始数据库连接,导致并发写冲突
-
<strong>dict</strong>里有lambda或闭包,deepcopy直接失败
所以得绕过默认序列化路径,自己控制每个子对象的生成逻辑。
如何用 __copy__ 和 __deepcopy__ 定制克隆行为?
Python 的 copy 模块会优先调用对象自身的这两个魔术方法。关键在于:不要在 <strong>deepcopy</strong> 里无脑递归调用 copy.deepcopy,而要显式决定每个属性怎么处理。
使用场景举例:一个带子节点的 Node 类,含业务数据(需深拷)、运行时状态(需重置)、共享服务引用(需复用):
class Node:
def __init__(self, name, data, cache=None, lock=None):
self.name = name
self.data = data # 需深拷
self.cache = cache # 全局共享,不拷
self.lock = lock # 不可拷,克隆时新建
self.children = [] # 子节点需递归克隆
<pre class="brush:python;toolbar:false;">def __deepcopy__(self, memo):
# 新建实例,跳过 __init__(避免重复初始化逻辑)
new_obj = Node.__new__(Node)
memo[id(self)] = new_obj # 防止循环引用死循环
# 手动控制每个字段
new_obj.name = self.name # 不变
new_obj.data = copy.deepcopy(self.data, memo) # 深拷业务数据
new_obj.cache = self.cache # 复用全局 cache
new_obj.lock = threading.Lock() # 新建锁
new_obj.children = [
copy.deepcopy(child, memo) for child in self.children
]
return new_obj
注意点:
- 必须传入
memo参数并使用它,否则嵌套引用会出错 -
<strong>new</strong>+ 手动赋值比super().<strong>deepcopy</strong>更可控 - 如果某字段是不可拷贝类型(如文件句柄),这里直接设为
None或默认值,而不是让它崩
何时该用原型工厂而非手动实现 __deepcopy__?
当对象树结构动态变化、或同一类有多种克隆策略(如“轻量克隆”只复制骨架、“全量克隆”连日志缓冲区也复制)时,硬编码 <strong>deepcopy</strong> 会迅速失控。
这时把克隆逻辑抽成独立工厂类更清晰:
class NodePrototypeFactory:
def __init__(self, prototype: Node):
self.prototype = prototype
<pre class="brush:python;toolbar:false;">def clone_light(self):
node = Node.__new__(Node)
node.name = self.prototype.name
node.data = {} # 清空业务数据
node.cache = self.prototype.cache
node.lock = threading.Lock()
node.children = []
return node
def clone_full(self):
return copy.deepcopy(self.prototype)
参数差异:
-
clone_light()返回的是新实例,但跳过耗时深拷(比如跳过data的大字典) -
clone_full()仍走标准deepcopy,但由工厂统一管控,方便打日志或加监控
性能影响:
- 工厂模式多一层间接调用,但换来的是策略可插拔——比如测试时用
clone_light,生产用clone_full,无需改模型代码
容易被忽略的边界:父类和内置容器的协作
如果 Node 继承自某个基类(比如 BaseEntity),而基类也实现了 <strong>deepcopy</strong>,务必检查是否调用了 super().<strong>deepcopy</strong>(memo)。没调用会导致基类字段丢失;调用了又可能触发你刚绕过的默认逻辑,形成冲突。
另外,别假设 list / dict 里的元素会自动按你的规则克隆——它们只是调用元素自己的 <strong>deepcopy</strong>。如果元素是第三方库对象(如 numpy.ndarray),它可能有自己的克隆机制,和你的逻辑不兼容。
可给出简短验证方式:
- 在
<strong>deepcopy</strong>开头加print(f"Cloning {type(self).<strong>name</strong>} at {id(self)}"),确认是否被预期调用 - 克隆后用
id(obj.cache) == id(clone.cache)验证共享字段是否真共享 - 对子节点逐个检查
isinstance(child, Node),防止某些路径误用copy.copy导致浅拷
原型模式的复杂点不在“怎么克隆”,而在“哪些不该克隆”和“谁来决定这个规则”。越早把克隆策略从模型里剥离开,后续扩展越不容易踩坑。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











