Python随笔|赋值、浅拷贝和深拷贝

python中为声明一个变量有三种方法:赋值、浅拷贝(shadow copy)、深拷贝(deep copy),相信每个pythoner或多或少都知道他们之间的区别,但在某些点上,还是会踩坑,这篇文章记录下所有关于这三者区别的疑问。
对比
在理解以上几个概念区别之前需要明确以下问题:
  • 赋值传递的是原始对象的地址
  • 函数传递形参也是传递地址,该地址指向原始对象,多进程的task函数除外,多进程中task函数的形参和原始对象是完全不同的两个对象,类似深拷贝(进程间内存独立)
  • 浅拷贝,除了显式地调用copy库,还可以使用如切片、list()、dict()等函数达到浅拷贝的效果(下面会给例子)。
  • 当我们讨论python中的拷贝时,默认针对的是可变对象,因为对于不可变对象来讲,拷贝是没有意义的,python解释器在启动时就将不可变对象放在内存池中了,它在内存的位置在解释器终止之前不会变。假设我们通过赋值、浅拷贝、深拷贝得到了一个新的对象,那么与原始对象相比,有以下结论:
/ 赋值 浅拷贝 深拷贝
id是否变化 × ? ?
修改对象本身是否互相影响 ? × ×
修改嵌套对象是否互相影响 ? ? ×
具体例子
a = ['a', 1, [1,2]] b = a c = copy.copy(a) d = copy.deepcopy(a)print(f'a is: {a} , id is: {id(a)}') print(f'b is: {b} , id is: {id(b)}') print(f'c is: {c} , id is: {id(c)}') print(f'd is: {d} , id is: {id(d)}')

输出:
a is: ['a', 1, [1, 2]] , id is: 140211144620672 b is: ['a', 1, [1, 2]] , id is: 140211144620672 c is: ['a', 1, [1, 2]] , id is: 140211144622016 d is: ['a', 1, [1, 2]] , id is: 140211144620992

【Python随笔|赋值、浅拷贝和深拷贝】可以看出,赋值得到的新对象id与原始对象id相同,拷贝得到的新对象id与原始对象不同。
# 对对象本身进行修改,即“最外层” a.append(2)print(f'a is: {a} , id is: {id(a)}') print(f'b is: {b} , id is: {id(b)}') print(f'c is: {c} , id is: {id(c)}') print(f'd is: {d} , id is: {id(d)}')

输出:
a is: ['a', 1, [1, 2], 2] , id is: 140211144620672 b is: ['a', 1, [1, 2], 2] , id is: 140211144620672 c is: ['a', 1, [1, 2]] , id is: 140211144622016 d is: ['a', 1, [1, 2]] , id is: 140211144620992

可以看出,修改对象本身对拷贝对象没有影响。
再修改嵌套对象:
# 修改嵌套对象 a[2].append(3)print(f'a is: {a} , id is: {id(a)}') print(f'b is: {b} , id is: {id(b)}') print(f'c is: {c} , id is: {id(c)}') print(f'd is: {d} , id is: {id(d)}')

输出:
a is: ['a', 1, [1, 2, 3], 2] , id is: 140211144620672 b is: ['a', 1, [1, 2, 3], 2] , id is: 140211144620672 c is: ['a', 1, [1, 2, 3]] , id is: 140211144622016 d is: ['a', 1, [1, 2]] , id is: 140211144620992

可以看出,修改嵌套对象,对浅拷贝对象有影响,对深拷贝对象无影响。
再看下不可变对象1的id:
print(f'1 id in a is {id(a[1])}') print(f'1 id in b is {id(b[1])}') print(f'1 id in c is {id(c[1])}') print(f'1 id in d is {id(d[1])}')

输出:
1 id in a is 140279957373168 1 id in b is 140279957373168 1 id in c is 140279957373168 1 id in d is 140279957373168

可以看出,所有对象的不可变对象指向了同一个位置,事实上,即便在同一个对象中相同的不可变对象也是指向同一个位置:
print(f'a is {a}') print(f'{id(a[1])}, {id(a[2][0])}')

输出:
a is ['a', 1, [1, 2, 3], 2] 140168057503984, 140168057503984

除了显示地使用copy.copy()获得一个浅拷贝对象,list()、dict()等对象工厂也可以获得一个浅拷贝对象:
a = ['a', 1, [1,2]] b = list(a) print(f'a is: {a} , id is: {id(a)}') print(f'b is: {b} , id is: {id(b)}')a[2].append(3) print(f'a is: {a} , id is: {id(a)}') print(f'b is: {b} , id is: {id(b)}')

输出:
a is: ['a', 1, [1, 2]] , id is: 139717765197952 b is: ['a', 1, [1, 2]] , id is: 139718287865280 a is: ['a', 1, [1, 2, 3]] , id is: 139717765197952 b is: ['a', 1, [1, 2, 3]] , id is: 139718287865280

可以看出,a与b的id不同,但修改嵌套对象会互相影响,所以b是a的浅拷贝对象。
切片也类似,是一个浅拷贝对象:
a = ['a', 1, [1,2]] b = a[-1:] print(f'a is: {a} , id is: {id(a)}') print(f'b is: {b} , id is: {id(b)}')a[2].append(3) print(f'a is: {a} , id is: {id(a)}') print(f'b is: {b} , id is: {id(b)}')

输出:
a is: ['a', 1, [1, 2]] , id is: 140294636448256 b is: [[1, 2]] , id is: 140294636449408 a is: ['a', 1, [1, 2, 3]] , id is: 140294636448256 b is: [[1, 2, 3]] , id is: 140294636449408

    推荐阅读