Skip to content

拷贝相关

父对象、子对象以及直接赋值、浅拷贝(copy)、深拷贝(deepcopy)-CSDN博客

python
a = 2
b = a
a = 3

内存的存储方式:

  • 先再内存中开栈存储 2 这个数据的空间
  • a 指向数据为 2 的内存空间 地址
  • b = a 此时 b 指向 2 的内存空间的 地址
  • a = 3 内存中开栈存储 3 这个数据的空间
  • 此时 a = 3 中,a 重新指向数据为 3 的内存地址
  • 问:此时 b 的值是多少?

总结:Python 是动态语言,变量只是对象的引用,不同于 C 中将值赋给变量。动态语言中,变量只是对象的引用,即对象本身有自己的内存空间,变量只是指向了该内存空间(所有的变量都类似指针)。

直接赋值

(变量、列表、字典、元组、集合等)直接赋值都只是原来对象的引用,赋值后改变原来对象,新对象也会 可能发生改变,具体情况如下:

变量:重新赋值

python
a = 3	# a 指向数据 3 的内存空间
b = a	# b 也指向数据 3 的内存空间
a = 4	# a 指向数据 4 的内存空间,b 指向的空间不变,b = 3

这里我们发现,赋值就等于 开辟了新的内存空间

列表:重新赋值

python
list_1 = [1, 2, 3]
list_2 = list_1
list_1 = [4, 5, 6]

print(list_1)  # [4, 5, 6]
print(list_2)  # [1, 2, 3]

在列表的直接赋值中,list_1[1, 2, 3] 变为 [4, 5, 6],相当于 重新开辟 了一个 [4, 5, 6] 对象的内存空间,并将 list_1 指向了该空间。之前指定的list_2 的指向内存空间没有发生改变,依旧指向 [1, 2, 3],所以 list_2 没有发生变化。

列表:插入新值

python
list_1 = [1, 2, 3]
list_2 = list_1
list_1.append(4)

print(list_1)  # [1, 2, 3, 4]
print(list_2)  # [1, 2, 3, 4]

在列表的插入新值中,由于通过 append(4) 方法在尾部插入新值,[1, 2, 3] 内存空间扩充,但 list_1 指向并 没有发生变化,同时该过程中,list_2 也依旧指向该空间,所以 list_2 一同发生变化。

因此,我们接下来就看重新赋值和原地修改对不同数据结构的影响。

元组

python
tup_1 = (1, 2, 3, 4, 5)
tup_2 = tup_1
tup_1 = tup_1 + (6, 7, 8, 9, 10) # tup_1 = (6, 7, 8, 9, 19)

print(tup_1)  # (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
print(tup_2)  # (1, 2, 3, 4, 5)

元组和列表不同,元组只要确定后,无论是 重新赋值 还是 插入新值tup_2 都不会发生变化。

字典

python
dict_1 = { "a": 11,"b": 22, "c": 33, "d": 44 }
dict_2 = dict_1
dict_1.popitem() # dict_1["a"] = 10


print(dict_1)  # {'a': 11, 'b': 22, 'c': 33}
print(dict_2)  # {'a': 11, 'b': 22, 'c': 33}

dict_1.popitem() 是对原来的字典 内存空间直接操作dict_1dict_2 都指向该空间,所以都会发生变换。因此无论是修改还是插入、删除这些在原内存空间上的操作,内存空间上的值都发生了变化。

当然,如果对 dict_1 重新赋值,那么 dict_2 的值是不会变的。

集合

python
set_1 = {1 ,2, 3, 4, 5}
set_2 = set_1
set_1.add(6)  # set_2 = cp.copy(set_1)

print(set_1)  # {1, 2, 3, 4, 5, 6}
print(set_2)  # {1, 2, 3, 4, 5, 6}

直接赋值同样也会修改 set_2 的值,因为修改了内存空间上的值。当然,如果对 set_1 重新赋值,那么 set_2 的值是不会变的。但我们注意到,copy.copy(set_1) 后,即使 set_1 修改了(不是重新赋值),set_2 依然不会变。

总结

  • 所有重新赋值都不会修改 b,因此对 a 重新赋值等价于 开辟新的内存空间
  • a 的修改会影响 变量、列表、集合、字典
  • 不会影响 元组

浅拷贝与深拷贝

  • 浅拷贝 copy():拷贝父对象,不会拷贝父对象内部的子对象
  • 深拷贝 deepcopy():完全拷贝了父对象及其子对象

举例:

python
import copy as cp

a = [1, 2, 3, 4, ['a', 'b']]

b = a      				 # 赋值。传对象的引用
c = a[:]    			 # 利用分片操作进行拷贝(浅拷贝)
d = cp.copy(a)  		 # 对象拷贝,浅拷贝
e = cp.deepcopy(a) 		 # 对象拷贝,深拷贝

a.append(5)     		 # 改动对象 a
a[4].append('c')         # 改动对象 a 中的 ['a', 'b'] 列表子对象

print(a)		# [1, 2, 3, 4, ['a', 'b', 'c'], 5]
print(b)		# [1, 2, 3, 4, ['a', 'b', 'c'], 5]
print(c)		# [1, 2, 3, 4, ['a', 'b', 'c']]
print(d)		# [1, 2, 3, 4, ['a', 'b', 'c']]
print(e)		# [1, 2, 3, 4, ['a', 'b']]

其实,浅拷贝,深拷贝底层还是 变量对内存空间指向层级 的不同。

直接复制:引用

数据都指向最顶层的内存空间:

python
a = [1, 2, 3, 4, ['a', 'b']]
b = a

20240123200612

浅拷贝

指向父对象的内存空间:

python
a = [1, 2, 3, 4, ['a', 'b']]
b = copy.copy(a)  # b = a[:] 分片操作

20240123200734

深拷贝

指向父对象及其所有子对象内存空间:

python
a = [1, 2, 3, 4, ['a', 'b']]
b = copy.deepcopy(a)

20240123200931

Released under the MIT License.