Python中的浅拷贝与深拷贝

前言

在Python内置的copy模块中定义了copy与deepcopy函数, 可以实现对任意Python对象的复制。如果需要自定义控制Python对象的复制行为,可以自定义__copy____deepcopy__方法, 分别对应浅拷贝与深拷贝。
 

浅拷贝与深拷贝的定义

  • copy.copy(x)

    • 返回对象x的浅拷贝备份。浅拷贝会先生成一个容器对象,然后将原始对象中包含的“对象引用”插入到新容器中(A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original)。
  • copy.deepcopy(x)

    • 返回对象的深拷贝。深拷贝会先生成一个容器对象,然后将原始对象中包含的元素的“副本”插入新容器中(A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original)。

浅拷贝拷贝的是“对象引用”, 深拷贝拷贝的是“元素副本”。元素副本是什么意思?可以通过下列实例验证:

import copy
x = [1, [2, 3], (4, 5)]
y = copy.copy(x)
z = copy.deepcopy(x)

# 对于嵌套型可变类型容器,深拷贝浅拷贝都会生成一个新容器
print(id(x), id(y), id(z))  # 2901579592640 2901579610816 2901579611136

# 第一个元素是列表,浅拷贝复制的是对象引用,深拷贝会复制值并新建一个同类型的对象
print(id(x[1]), id(y[1]), id(z[1]))  # 2901579568960 2901579568960 2901579611520

# 第二个元素是元组,深拷贝与浅拷贝都指向同一对象
print(id(x[2]), id(y[2]), id(z[2])) # 2901579567296 2901579567296 2901579567296

从上面实例可以看出,深拷贝与浅拷贝的最大影响因素应该是对象的类型:“可变类型还是不可变类型”。对于不可变对象,浅复制与深复制作用相同;但是对于“可变类型”,深复制与浅复制差异就非常大,对于浅拷贝,备份与母本包含的是同一对象的引用,当被引用的对象发生改变时,这种改变会同时反映到母本与浅复制对象上。 而深拷贝会复制母本的值并新建一个同类型的对象,不存在引用同一对象的引起的问题。
 

常见的类型构造方法

    x = [1, 2, 3]; y = list(x); print(id(x), id(y), x == y, id(x) == id(y))  # 2901579599424 2901579609856 True False
    x = (1, 2, 3); y = tuple(x); print(id(x), id(y), x == y, id(x) == id(y))  # 2901579538624 2901579538624 True True
    x = "123"; y = str(x); print(id(x), id(y), x == y, id(x) == id(y))  # 2901575533296 2901575533296 True True
    x = {"a": 1}; y = dict(x); print(id(x), id(y), x == y, id(x) == id(y))  # 2901579611904 2901580800640 True False
    x = set([1, 2, 3]); y = set(x); print(id(x), id(y), x == y, id(x) == id(y))  # 2901583446080 2901583446304 True False

对于不可变类型strtuple,类型构造方法没有创建新对象;对于可变类型listdictset,类型构造方法会创建新对象。
 

亲密字符串实例

  • 采用shallow copy时,会得到错误的结果
import copy

def buddyStrings(A, B):
    """
    亲密字符串
    :type A: str
    :type B: str
    :rtype: bool
    """
    length = len(A)
    res = []
    temp = list(A)
    for i in range(length):
        for j in range(i+1, length):
            # 采用shallow copy的方式将temp的值赋给变量 “C"
            C = temp 
            C[j], C[i] = C[i], C[j]
            res.append(''.join(C) == B)
    if sum(res) >=1:
        return True
    else:
        return False

buddyStrings("aaaaaaabc", "aaaaaaacb")
# 结果返回Fales,但应该是True
  • 采用deep copy的方式得到正确的结果
import copy

def buddyStrings(A, B):	
    length = len(A)
    res = []
    temp = list(A)
    for i in range(length):
        for j in range(i+1, length):
            # 采用deep copy的方式将temp的值赋给变量 “C"
            C = copy.deepcopy(temp) 
            C[j], C[i] = C[i], C[j]
            res.append(''.join(C) == B)
    if sum(res) >=1:
        return True
    else:
        return False
        
buddyStrings("aaaaaaabc", "aaaaaaacb")
# 结果返回是True

总结

  1. 深拷贝与浅拷贝的重要影响因素是对象的类型——可变类型与不可变类型。
  2. 明确对象的深拷贝与浅拷贝结果,能查看__copy____deepcopy__源码定义是最好的。
  3. 另外补充:Python中的赋值语句从“等号右侧”开始执行,右侧表达式执行完成之后,会将“等号左侧”的变量标识符绑定到表达式返回对象上。如果为同一对象绑定多个“标识符”,则可以将其它的标识符看作“别名”。需要注意的是,在赋值语句中,是没有复制右侧的对象的操作,只是将左侧的标识符绑定到右侧的对象上(Assignment statements in Python do not copy objects, they create bindings between a target and an object)。
     

参考资料

  1. 《流程的Python》第8章——对象引用、可变性与垃圾回收
  2. https://docs.python.org/3.7/library/copy.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值