介绍
deepcopy是用来进行深层复制的, 但是在实际中可能遇到, 复制一个对象后, 某些属性没了, 因此看看源码, 顺便记录下来.
Python 版本: 3.6
copy模块位置: copy.py (Python的lib包里面)
deepcopy原理概括: deepcopy的逻辑要根据对象的不同发生变化, 比如float, int等, 直接拷贝一个就行了, 若是一个复杂的对象, 要将各个属性都要复制才行. deepcopy对象潜在问题有, 一是可能存在循环引用(比如对象的某个属性引用了自身), 二是如何构建新的对象作为对象的一个复制.
问题一的解决是通过一个memo参数实现的, 如果发现某个id对应的对象复制过了, 就不再复制了.
问题二的解决是通过对象的__reduce__
和__reduce_ex__
等函数, 得到对象的参数表示, 然后复制这个参数表示, 再通过_reconstruce
函数构建一个对象, 就得到了对象的一个deepcopy. 也可以通过__deepcopy__
函数自定义一个对象的复制逻辑.
源码注释
"""
deepcopy的主要逻辑在下面的函数.
deepcopy的文档: https://docs.python.org/3/library/copy.html
"""
def deepcopy(x, memo=None, _nil=[]):
"""Deep copy operation on arbitrary Python objects.
See the module's __doc__ string for more info.
"""
if memo is None:
memo = {
}
# memo 用于记录对象是否被复制过了, 可以防止对象出现循环引用导致无限复制的情景.
d = id(x)
y = memo.get(d, _nil)
if y is not _nil:
return y
cls = type(x)
copier = _deepcopy_dispatch.get(cls) # 一些内置的数据类型有特定的复制函数, 比如list等, 在copy.py中可以找到其定义.
if copier:
y = copier(x, memo)
else:
try:
issc = issubclass(cls, type) # int, float等类型, 在复制的时候直接是自身, 因此不用特别的进行复制.
except TypeError: # cls is not a class (old Boost; see SF #502085)
issc = 0
if issc:
y = _deepcopy_atomic(x, memo)
else:
copier = getattr(x, "__deepcopy__", None)
if copier:
y = copier(memo) # 对象可以自己定义deepcopy的逻辑
else:
# 如果没有定义, 则通过reduce进行对象的深层复制. reduce和_reconstruct是对应的, reduce是对象的序列化, _reconstruct是根据对象的序列化表示构建对象. 这里是deepcopy的关键, 关于序列化和反序列化的操作, 和pickle的逻辑是一样的. 参考 pickle的文档: https://docs.python.org/3/library/pickle.html
reductor = dispatch_table.get(cls)
if reductor:
rv = reductor(x)
else:
reductor = getattr(x, "__reduce_ex__", None)
if reductor:
rv = reductor(4)
else:
reductor = getattr(x, "__reduce__", None)
if reductor:
rv = reductor()
else:
raise Error(
"un(deep)copyable object of type %s" % cls)
# 重新构建对象
if isinstance(rv, str):
y = x
else:
y = _reconstruct(x, memo, *rv)
# If is its own copy, don't memoize.
if y is not x:
memo[d] = y
_keep_alive(x, memo) # Make sure x lives at least as long as d
return y
"""
_reconstruct是根据reduce得到的对象表示进行构建对象. 在deepcopy的情况下, 是复制对象序列化的表示, 然后根据这个表示构建对象的复制, 整个过程是迭代进行的.
"""
def _reconstruct(x, memo, func, args,
state=None, listiter=None, dictiter=None,
deepcopy=deepcopy):
deep = memo is not None
if deep and args:
args = (deepcopy(arg, memo) for arg in args)
y = func(*args) # 对象的构建, 除了x, 和memo, 其他几个参数都是reduce得到的, 这里调用函数func默认是内置的, 不是对象的构造函数(__init__).
if deep:
memo[id(x)] = y
if state is not None:
if deep:
state = deepcopy(state, memo)
if hasattr(y, '__setstate__'):
y.__setstate__(state) # setstate的语义见pickle的文档.
else:
if isinstance(state, tuple) and len(state) == 2:
state, slotstate = state
else:
slotstate = None
if state is not None:
y.__dict__.update(state)
if slotstate is not None:
for key, value in slotstate.items():
setattr(y, key, value)
if listiter is not None:
if deep:
for item in listiter:
item = deepcopy(item, memo)
y.append(item)
else:
for item in listiter:
y.append(item)
if dictiter is not None:
if deep:
for key, value in dictiter:
key = deepcopy(key, memo)
value = deepcopy(value, memo)
y[key] = value
else:
for key, value in dictiter:
y[key] = value
return y