Python里面如何拷贝一个对象?
Python 面试要是被问到“怎么拷贝一个对象?”你要是只说 copy.copy() 和 copy.deepcopy(),面试官可能会点点头,但心里想的可能是:“就这?”
Python里拷贝对象,不只是 copy 模块的事儿,背后牵扯的是可变对象、不可变对象、浅拷贝、深拷贝、引用传递、魔法方法,甚至还有一些隐藏的小坑和面试官挖好的“陷阱题”。
先从最表层的说起,= 这个操作符其实压根儿不算拷贝,它是“引用赋值”。举个栗子👇:
a = [1, 2, 3]
b = a
b.append(4)
print(a) # [1, 2, 3, 4]
很多新手在这栽跟头,以为改了 b 不影响 a,结果俩人共用一个内存地址,b动了,a就跟着抖一抖。这不是拷贝,这是拉帮结派,换个名字共用一个家。
所以,如果你真的想拷贝一个列表,最直接的方式是:
a = [1, 2, 3]
b = a.copy() # 等价于 b = list(a) 或 b = a[:]
这个是“浅拷贝”,也就是说顶层是新的,但里面嵌套的对象还是老的引用。比如这个例子就有坑:
a = [[1, 2], [3, 4]]
b = a.copy()
b[0].append(9)
print(a) # [[1, 2, 9], [3, 4]]
你以为你换了人,结果还在用前女友家Wi-Fi。
这种场景下就得用“深拷贝”:
import copy
a = [[1, 2], [3, 4]]
b = copy.deepcopy(a)
b[0].append(9)
print(a) # [[1, 2], [3, 4]]
这才是真正的“老死不相往来”。
说到这,面试官要是问你 copy.copy() 和 copy.deepcopy() 有啥区别,别死记定义,关键是举个反例说清楚。你说:“如果对象里面嵌套了别的可变对象,那 copy.copy() 只是拷贝了最外层,里面的还是原来的引用;deepcopy() 是连根拔起,从你内存深处干净利落地全都复制一遍。”
再进阶点,如果你碰上的是一个自定义类,光靠 copy() 和 deepcopy() 还不够。你要是类里面用了 __slots__,或者拷贝的对象里有文件句柄、网络连接这种“不能拷贝的资源”,你就得自己实现 __copy__ 和 __deepcopy__。
比如:
classPerson:
def__init__(self, name, friends):
self.name = name
self.friends = friends
def__copy__(self):
print("浅拷贝 Person")
return Person(self.name, self.friends)
def__deepcopy__(self, memo):
print("深拷贝 Person")
import copy
return Person(copy.deepcopy(self.name, memo),
copy.deepcopy(self.friends, memo))
这个时候你面试官可能就开始点头了:“嗯,这人确实懂点。”
但面试官要是更狠一点,给你丢个“闭环题”:“你知道拷贝对象和 __init__() 有啥关系么?”
哈哈,这就好玩了——copy.copy() 是不会触发 __init__() 的,因为它是通过 __new__() 创建新对象,然后复制原对象的 __dict__ 或 __slots__。这就意味着你可以在不调用初始化逻辑的情况下拷贝对象,非常适合那种初始化非常贵、但状态想复用的场景。
最后讲一个反套路的回答。要是你碰到的是个老江湖面试官,说“你还知道其他方法吗?”
你可以抛出一颗小炸弹:“其实有些时候,我根本不会去拷贝对象,而是换个思路——用不可变数据结构,让状态天然就是安全的。比如我用 namedtuple 或者 dataclass(frozen=True),对象压根儿不能改,还拷个啥?”
这句话既装逼又实用,还顺带展示你对 Python 编程范式的理解。
下次再遇到这个题,不要干巴巴地背API,得结合实际业务场景说点干货。比如你说你在处理某个缓存系统、数据快照、线程安全对象时怎么用 copy,那才是能把面试官带进你节奏里的招儿。
-END-
我为大家打造了一份RPA教程,完全免费:https://www.songshuhezi.com/rpa.html
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB,点击下方公众号回复关键字 python 全部免费领