Python——从源码分析深浅拷贝特点

本文深入探讨了Python中深拷贝和浅拷贝的区别,详细分析了不可变类型和可变对象在拷贝过程中的行为,通过源码解读了copy模块的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

感谢互联网上无私的知识贡献者——
本文参考了以下博客:
Python之美[从菜鸟到高手]–浅拷贝、深拷贝完全解读(copy源码分析)

欢迎大家指出本文中的不足,尽量及时更改,避免误导他人,Thanks♪(・ω・)ノ

环境

Python3.8

深、浅拷贝特点

对象类型深拷贝返回值浅拷贝返回值
不可变本身(不包括tuple)本身
可变嵌套拷贝对象1表层拷贝对象2

可能有点抽象,举个例子:

ele = [4]
list_0 = [1, 2, 3, ele]
# 形象点就是copy搬运个网站,里头有链接,也只是原原本本复制链接
# deepcopy在此之上,还会把链接指向的网站内容也复制了
# 例如:
copy_list_0 = [1, 2, 3, ele]
deepcopy_list_0 = [1, 2, 3, [4]]

注意:深拷贝能够递归拷贝,所以其会引发新的问题——tuple类组合内含有可变类型该如何处理?
结论:用tunple函数重新生成一个元组,并返回。

源码Document

以下为copy模块的说明截取:

The difference between shallow and deep copying is only relevant for
compound objects (objects that contain other objects, like lists or
class instances).

- A shallow copy constructs a new compound object and then (to the
  extent possible) inserts *the same objects* into it that the
  original contains.

- A deep copy constructs a new compound object and then, recursively,
  inserts *copies* into it of the objects found in the original.

翻译:
浅复制和深复制的不同仅仅是对组合对象来说,所谓的组合对象就是包含了其它对象的对象,如列表,类实例。

–浅拷贝将构造一个新的组合对象,然后将所包含的对象直接插入到新的组合对象中

–深拷贝讲构造一个新的组合对象,然后递归的拷贝所包含的对象并插入到新的组合对象中

验证及源码说明

不可变类型

验证:对下列immutable进行深浅拷贝,对比源头id,验证均相同,得证。(输出略)

import copy

im_int = 0
im_bool = False
im_string = '0'
im_tuple = (0, 0)

if      copy.copy(im_int) is im_int and \
        copy.copy(im_bool) is im_bool and \
        copy.copy(im_string) is im_string and \
        copy.copy(im_tuple) is im_tuple and \
        copy.deepcopy(im_int) is im_int and \
        copy.deepcopy(im_bool) is im_bool and \
        copy.deepcopy(im_string) is im_string and \
        copy.deepcopy(im_tuple) is im_tuple:
    print('immutable类型变量深浅备份仅返回本身')
else:
    print('error')

浅拷贝源码

_copy_dispatch = d = {}
# 如果x为immutable类型,返回本身
def _copy_immutable(x):
    return x
# 将包括immutable在内的,所有不可拷贝类型添加进字典_copy_dispatch(d)
for t in (type(None), int, float, bool, complex, str, tuple,
          bytes, frozenset, type, range, slice, property,
          types.BuiltinFunctionType, type(Ellipsis), type(NotImplemented),
          types.FunctionType, weakref.ref):
    d[t] = _copy_immutable

分析:copy模块先行设置不可拷贝类型的key为_copy_immutable方法,该方法返回x本身。

def copy(x):
    """Shallow copy operation on arbitrary Python objects.

    See the module's __doc__ string for more info.
    """

    # 获得x类型
    cls = type(x)
    # 获取该类型的处理方法,对于immutable类的type,均返回x本身
    copier = _copy_dispatch.get(cls)
    if copier:
        return copier(x)

分析:在copy得到带拷贝目标x后,提取其type,并获取字典_copy_dispatch对应的处理方法,对于immutable类型则返回_copy_immutable方法——直接返回x本身。

深拷贝源码

# 同浅拷贝,获取对应方法并执行
copier = _deepcopy_dispatch.get(cls)
if copier is not None:
    y = copier(x, memo)
def _deepcopy_atomic(x, memo):
    return x
# d = _deepcopy_dispatch
d[type(None)] = _deepcopy_atomic
d[type(Ellipsis)] = _deepcopy_atomic
d[type(NotImplemented)] = _deepcopy_atomic
d[int] = _deepcopy_atomic
d[float] = _deepcopy_atomic
d[bool] = _deepcopy_atomic
d[complex] = _deepcopy_atomic
d[bytes] = _deepcopy_atomic
d[str] = _deepcopy_atomic
d[types.CodeType] = _deepcopy_atomic
d[type] = _deepcopy_atomic
d[types.BuiltinFunctionType] = _deepcopy_atomic
d[types.FunctionType] = _deepcopy_atomic
d[weakref.ref] = _deepcopy_atomic
d[property] = _deepcopy_atomic

分析:这里的d就是_deepcopy_dispatch。深拷贝对于除tuple外的immutable类型的处理相似,不做赘述。

以下重点看深拷贝对不可变类型tuple的处理:
源码及分析:

def _deepcopy_tuple(x, memo, deepcopy=deepcopy):
    # 使用deepcopy对x内的组合进行复制,得到y
    y = [deepcopy(a, memo) for a in x]
    # We're not going to put the tuple in the memo, but it's still important we
    # check for it, in case the tuple contains recursive mutable structures.
    try:
        return memo[id(x)]
    except KeyError:
        pass
    # 对比x、y中的元素,如果存在一个id不相等,则说明包含有可变类型
    for k, j in zip(x, y):
        if k is not j:
            # 调用tuple重新生成元组
            y = tuple(y)
            break
    else:
        y = x
    return y
d[tuple] = _deepcopy_tuple

分析:通过调用deepcopy递归赋值tuple内的元素,如果包含可变元素,deepcopy会用响应的方法进行复制,如_deepcopy_list方法,并通过tuple(y)重新生成一个元组并返回。如果不包含,则返回本身。

可变对象

验证:对下列可变类型进行copy,对比id,结果输出下列语句,得证。

ele = [4, 5]
mu_list = [1, 2, 3, ele]
mu_dic = {1: 1, 2: 2}

if      copy.copy(mu_list) is not mu_list and \
        copy.copy(mu_dic) is not mu_dic and \
        copy.deepcopy(mu_list) is not mu_list and \
        copy.deepcopy(mu_dic) is not mu_dic:
    print('mutable类型变量深浅备份返回新的对象')

浅拷贝源码:

# 以下就直接添加对应模块的copy方法到copy模块所维护的d中
d[list] = list.copy
d[dict] = dict.copy
d[set] = set.copy
d[bytearray] = bytearray.copy

深拷贝源码:

def _deepcopy_list(x, memo, deepcopy=deepcopy):
    y = []
    memo[id(x)] = y
    append = y.append
    for a in x:
        append(deepcopy(a, memo))
    return y
d[list] = _deepcopy_list

def _deepcopy_tuple(x, memo, deepcopy=deepcopy):
    y = [deepcopy(a, memo) for a in x]
    # We're not going to put the tuple in the memo, but it's still important we
    # check for it, in case the tuple contains recursive mutable structures.
    try:
        return memo[id(x)]
    except KeyError:
        pass
    for k, j in zip(x, y):
        if k is not j:
            y = tuple(y)
            break
    else:
        y = x
    return y
d[tuple] = _deepcopy_tuple

def _deepcopy_dict(x, memo, deepcopy=deepcopy):
    y = {}
    memo[id(x)] = y
    for key, value in x.items():
        y[deepcopy(key, memo)] = deepcopy(value, memo)
    return y
d[dict] = _deepcopy_dict
if PyStringMap is not None:
    d[PyStringMap] = _deepcopy_dict

分析:调用deepcopy进行递归复制可变类型中的元素,返回复制填充完毕后的新y。尤其要注意的是tuple是否包含可变类型,如果不包含就返回x本身,如果包含,就返回新生成的tuple。

深浅拷贝区别:

ele = [4, 5]
mu_list = [1, 2, 3, ele]
mu_dic = {1: 1, 2: 2}

copy_mu_list = copy.copy(mu_list)
deepcopy_mu_list = copy.deepcopy(mu_list)
mu_list[3][0] = 999
if copy_mu_list == [1, 2, 3, [999, 5]]:
    print('浅拷贝')
if deepcopy_mu_list == [1, 2, 3, [4, 5]]:
    print('深拷贝')

结论:运行后,输出两个string——修改了列表里头的列表,发现只有浅拷贝受到了影响,即浅拷贝把ele照搬过去挂个名字,而深拷贝还进行了ele内部的搬运,生产了一个新的ele。

结束语

深浅拷贝——本质在于其是否使用了递归来进行复制。


  1. 递归拷贝目标对象的包含的对象 ↩︎

  2. 仅拷贝目标对象的包含的对象 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值