Python - 拷贝 - 浅拷贝(Shallow Copy)和深拷贝(Deep Copy)

Posted Tisfy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python - 拷贝 - 浅拷贝(Shallow Copy)和深拷贝(Deep Copy)相关的知识,希望对你有一定的参考价值。

Python - 拷贝 - 浅拷贝(Shallow Copy)和深拷贝(Deep Copy)

前言

假设我以这样的方式创建一个3 x 5的二维数组:

a = [[0] * 5] * 3

然后我修改 a [ 2 ] [ 3 ] a[2][3] a[2][3]的值为 1 1 1

a[2][3] = 1

结果会发现数组 a a a中第二维坐标为 3 3 3的数全部被修改为了 1 1 1,而没有发生“第一维坐标为 2 2 2的数全部被改成了 1 1 1

print(a)  # [[0, 0, 0, 1, 0], [0, 0, 0, 1, 0], [0, 0, 0, 1, 0]]

原因

这就涉及到了Python中的拷贝机制。

Python中的数据按照其是否可以更改,可以分为两类:

  • 可变类型包括列表(list)、字典(dict)和集合(set)
  • 不可变类型包括整数(int)、浮点数(float)、布尔值(bool)、元组(tuple)和字符串(str)

深拷贝: 对于不可变类型(例如整数)进行复制操作时,会产生一个新的对象。对新对象的更改不会对旧对象造成影响:

a = 2
b = a
b = 1
print(a, b)  # 2 1
print(id(a), id(b))  # 2474931349840 2474931349808  # 不同

浅拷贝: 然而对于可变类型(例如列表)进行复制时,只会将对象的引用复制一份,它们实际指向同意对象。因此修改新的对象会对旧对象产生影响:

a = [1, 2, 3]
b = a
b[2] = 0
print(a, b)  # [1, 2, 0] [1, 2, 0]
print(id(a), id(b))  # 2537310019904 2537310019904  # 相同

注意对新对象的修改是指修改对象中的一部分,而不是让新对象指向另一个对象

a = [1, 2, 3]
b = a
b[0] = 0  # 修改对象中的一部分,这时a = [0, 2, 3]
b = [0]  # b指向了一个新的对象,原来的对象并没有被修改,这时a = [0, 2, 3]

这就解释了前言中的问题

[ 0 ] ∗ 5 [0] * 5 [0]5是将 0 0 0复制为5份, 0 0 0是不可变的整数,因此新列表 [ 0 , 0 , 0 , 0 , 0 ] [0, 0, 0, 0, 0] [0,0,0,0,0]中的每个 0 0 0都是独立的,修改其中一个 0 0 0不会影响到其他 0 0 0的值

但是 [ [ 0 , 0 , 0 , 0 , 0 ] ∗ 3 ] [[0, 0, 0, 0, 0] * 3] [[0,0,0,0,0]3]是将 [ 0 , 0 , 0 , 0 , 0 ] [0, 0, 0, 0, 0] [0,0,0,0,0]复制为5份, [ 0 , 0 , 0 , 0 , 0 ] [0, 0, 0, 0, 0] [0,0,0,0,0]是可变的列表,因此实质上是创建了 3 3 3个指向 [ 0 , 0 , 0 , 0 , 0 ] [0, 0, 0, 0, 0] [0,0,0,0,0]的对象,因此修改其中一个,另外两个也会随之变化。

但是:

a = [0] * 5
for i in range(5):
    print(id(a[i]), end=' ')
# 2977374300432 2977374300432 2977374300432 2977374300432 2977374300432  # 完全相同!!!
print(id(a[0] == id(a[1])))  # True
a[0] = 1
print(a)  # [1, 0, 0, 0, 0]
print(id(a[0]))  # 2977374300464
print(id(a[1]))  # 2977374300432
print(id(a[2]))  # 2977374300432
print(id(a[0]) == id(a[1]))  # False

也许是Py的优化?只有当修改不可变元素时才真的深拷贝?

TODO: import copy

可以研究一下 copy.copy()和copy.deepcopy()

原创不易,转载请附上原文链接哦~
Tisfy:https://letmefly.blog.csdn.net/article/details/129972641

deep copy and shallow copy

链接
A:浅拷贝就是成员数据之间的一一赋值:把值赋给一一赋给要拷贝的值。但是可能会有这样的情况:对象还包含资源,这里的资源可以值堆资源,或者一个文件。。当值拷贝的时候,两个对象就有用共同的资源,同时对资源可以访问,这样就会出问题。深拷贝就是用来解决这样的问题的,它把资源也赋值一次,使对象拥有不同的资源,但资源的内容是一样的。对于堆资源来说,就是在开辟一片堆内存,把原来的内容拷贝。
如果你拷贝的对象中引用了某个外部的内容(比如分配在堆上的数据),那么在拷贝这个对象的时候,让新旧两个对象指向同一个外部的内容,就是浅拷贝;如果在拷贝这个对象的时候为新对象制作了外部对象的独立拷贝,就是深拷贝
引用和指针的语义是相似的,引用是不可改变的指针,指针是可以改变的引用。其实都是实现了引用语义。
深拷贝和浅拷贝的区别是在对象状态中包含其它对象的引用的时候,当拷贝一个对象时,如果需要拷贝这个对象引用的对象,则是深拷贝,否则是浅拷贝。
COW语义是“深拷贝”与“推迟计算”的组合,仍然是深拷贝,而非浅拷贝,因为拷贝之后的两个对象的数据在逻辑上是不相关的,只是内容相同。
举个简单的例子:
当你实现一个Composite Pattern,你通常都会实现一个深拷贝(如果需要拷贝的话),很少有要求同的Composite共享Leaf的;
而当你实现一个Observer Pattern时,如果你需要拷贝Observer,你大概不会去拷贝Subject,这时就要实现个浅拷贝。
是深拷贝还是浅拷贝,并不是取决于时间效率、空间效率或是语言等等,而是取决于哪一个是逻辑上正确的。
1:没有虚方法和虚基类
2:所有直系基类的copy constructor都是无代价的
3:所有成员的copy constructor都是无代价的
这时它的copy constructor是无代价的,相当于用memcpy实现。
判断它是深拷贝还是浅拷贝,还是要根据类的实现。
A:浅拷贝就是成员数据之间的一一赋值:把值赋给一一赋给要拷贝的值。但是可能会有这样的情况:对象还包含资源,这里的资源可以值堆资源,或者一个文件。。当值拷贝的时候,两个对象就有用共同的资源,同时对资源可以访问,这样就会出问题。深拷贝就是用来解决这样的问题的,它把资源也赋值一次,使对象拥有不同的资源,但资源的内容是一样的。对于堆资源来说,就是在开辟一片堆内存,把原来的内容拷贝。
如果你拷贝的对象中引用了某个外部的内容(比如分配在堆上的数据),那么在拷贝这个对象的时候,让新旧两个对象指向同一个外部的内容,就是浅拷贝;如果在拷贝这个对象的时候为新对象制作了外部对象的独立拷贝,就是深拷贝
引用和指针的语义是相似的,引用是不可改变的指针,指针是可以改变的引用。其实都是实现了引用语义。
深拷贝和浅拷贝的区别是在对象状态中包含其它对象的引用的时候,当拷贝一个对象时,如果需要拷贝这个对象引用的对象,则是深拷贝,否则是浅拷贝。
COW语义是“深拷贝”与“推迟计算”的组合,仍然是深拷贝,而非浅拷贝,因为拷贝之后的两个对象的数据在逻辑上是不相关的,只是内容相同。
举个简单的例子:
当你实现一个Composite Pattern,你通常都会实现一个深拷贝(如果需要拷贝的话),很少有要求同的Composite共享Leaf的;
而当你实现一个Observer Pattern时,如果你需要拷贝Observer,你大概不会去拷贝Subject,这时就要实现个浅拷贝。
是深拷贝还是浅拷贝,并不是取决于时间效率、空间效率或是语言等等,而是取决于哪一个是逻辑上正确的。
1:没有虚方法和虚基类
2:所有直系基类的copy constructor都是无代价的
3:所有成员的copy constructor都是无代价的
这时它的copy constructor是无代价的,相当于用memcpy实现。
判断它是深拷贝还是浅拷贝,还是要根据类的实现。

以上是关于Python - 拷贝 - 浅拷贝(Shallow Copy)和深拷贝(Deep Copy)的主要内容,如果未能解决你的问题,请参考以下文章

深拷贝(deep clone)与浅拷贝(shallow clone)

Python 深浅拷贝 (Shallow copy and Deep copy in Python)

浅拷贝深拷贝UDP协议反射

javaScript| 对象的拷贝

deep copy and shallow copy

JavaScript深拷贝和浅拷贝 及 JSON.parse(JSON.stringify()) 的缺陷