Python:深拷贝与浅拷贝

发布时间 2023-10-23 16:47:51作者: 七落安歌

python:深拷贝与浅拷贝

一、了解几个概念

  • 变量:是一个系统表的元素,拥有指向对象的连接空间

  • 对象:被分配的一块内存,存储所代表的值

  • 引用:是自动形成的从变量到对象的指针

  • 类型:属于对象,而非变量

  • 不可变对象:一旦创建就不可修改的对象(值内存地址固定后不可以再修改其值),包括字符串、元组、数值类型(整型、浮点型)、布尔类型

该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。

  • 可变对象:可以修改的对象(值内存地址固定后还可以修改其值),包括列表、字典、集合

该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的地址,通俗点说就是原地改变。

当我们写:

a = 'python'

① 创建变量 a (栈)

② 创建一个对象(分配一块内存空间),来存储值“python” (数据区)

③ 将变量与对象,通过指针连接起来,从变量到对象的连接称之为引用(变量引用对象)

image-20231019215615031

赋值:只是复制了新对象的引用,不会开辟新的内存空间(将原有数据打上新的标签)

image-20231019220233726

二、浅拷贝

拷贝就是复制操作,其拷贝的值是一样的,重点观察引用关系的变化

浅拷贝:创建新对象,其内容是原对象的引用

浅拷贝之所以称为浅拷贝,是因为它仅仅只拷贝一层,拷贝了最外围的对象本身,内部的元素都只是拷贝一个引用而已。

基本语法:

import copy
copy.copy(要拷贝的变量名称)

可变数据类型的浅拷贝:

import copy

# 对于可变数据类型的浅拷贝
list1 = [1, 3, 5]
list2 = copy.copy(list1)
print(list1, id(list1))  # [1, 3, 5] 2029785917952
print(list2, id(list2))  # [1, 3, 5] 2029788974656
  • 对于简单的可变数据类型,浅拷贝相当于将原对象的值进行拷贝,需要在内存空间中开辟一块新的内存空间

image-20231019221633149

import copy

list1 = [1, 3, 5, [7, 9]]
list2 = copy.copy(list1)
print(list1, id(list1))  # [1, 3, 5, [7, 9]] 1757227908928
print(list2, id(list2))  # [1, 3, 5, [7, 9]] 1757227822720

# 但是对于内层[7.9],这也是一个列表,也需要占用内存地址,那么其拷贝过程的内存是否相同呢
print(id(list1[3]))  # 2710364155712
print(id(list2[3]))  # 2710364155712
  • 对于复杂的可变数据类型,浅拷贝只能拷贝可变数据类型的最外层对象,而无法拷贝内层对象,所以只需要为最外层对象开辟内存空间,内层对象拷贝之后的引用关系和原对象保持不变

image-20231019222741554

  • 结论:浅拷贝能力有限,只能拷贝最外层对象(只需要为最外层对象开辟内存空间),而无法拷贝内层对象

不可变数据类型的浅拷贝

import copy

tuple1 = (1, 3, 5)
tuple2 = copy.copy(tuple1)

print(tuple1, id(tuple1))  # (1, 3, 5) 1831160185728
print(tuple2, id(tuple2))  # (1, 3, 5) 1831160185728
  • 对于简单的不可数据类型,由于不可变数类型地址一旦固定,其值就无法改变了,又由于浅拷贝需要把自身的对象空间赋值给另外一个对象,为了保持数据一致,只能让其指向相同的内存空间(不需要额外开辟内存空间)

image-20231019224248298

  • 对复杂的不可变数据类型,浅拷贝也只能拷贝最外层对象,无法拷贝内层对象

浅拷贝的三种形式:

切片操作、工厂函数(数据类型转换)、copy模块中的copy函数

切片操作:list1 = list[ : ] 或 list1 = [i for i in list ]

工厂函数:list1 = list(list2)

copy函数:list1 = copy.copy(list)

浅拷贝案例:

a = [1, 3, 5, [7, 9]]
b = a[:]
a[1] = 2
a[3][1] = 10

print(b)
# 问b输出结果多少?
# [1, 3, 5, [7, 10]]

浅拷贝小结:

1)当浅复制的值是不可变对象(字符串、元组、数值类型)时和“赋值”的情况一样,对象的id值(id()函数用于获取对象的内存地址)与浅复制原来的值相同。

2)当浅复制的值是可变对象(列表、字典、集合)时会产生一个“不是那么独立的对象”存在。有两种情况:

第一种情况:复制的对象中无复杂子对象,原来值的改变并不会影响浅复制的值,同时浅复制的值改变也并不会影响原来的值。原来值的id值与浅复制原来的值不同。

第二种情况:复制的对象中有复杂子对象(例如列表中的一个子元素是一个列表),如果不改变其中复杂子对象,浅复制的值改变并不会影响原来的值。 但是改变原来的值中的复杂子对象的值会影响浅复制的值。

三、深拷贝

深拷贝:与浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。深拷贝拷贝出来的对象是一个全新的对象,不再与原来的对象有任何关系。

所以改变原有被复制对象不会对已经复制出来的新对象产生影响。

深拷贝只有一种形式:使用copy模块中的deepcopy函数

import copy
data = copy.deepcopy(data)

简单的可变类型深拷贝:

import copy

a = [1, 3, 5]
b = copy.deepcopy(a)
print(a, id(a))  # [1, 3, 5] 2434688069312
print(b, id(b))  # [1, 3, 5] 2434690765888

复杂的可变类型深拷贝:

import copy

a = [1, 3, 5, [7, 9]]
b = copy.deepcopy(a)
print(a, id(a))  # [1, 3, 5, [7, 9]] 2162810741504
print(b, id(b))  # [1, 3, 5, [7, 9]] 2162810727424
print(id(a[3]))  # 2162810727040
print(id(b[3]))  # 2162810727232
  • 对于可变数据类型的深拷贝,深拷贝拷贝了所有的数据并开辟对应的内存空间储存。

不可变数据类型的深拷贝:

import copy

a = (1, 3, 5, (7, 9))
b = copy.deepcopy(a)
print(a, id(a))  # (1, 3, 5, (7, 9)) 2029346995712
print(b, id(b))  # (1, 3, 5, (7, 9)) 2029346995712

print(id(a[3]))  # 2029346562432
print(id(b[3]))  # 2029346562432
  • 对于不可变数据类型的深拷贝,不管是简单的还是复杂的,深拷贝都只能对象的引用关系,他们指向了相同的内存空间

四、深浅拷贝中的特殊情况

案例1:可变嵌套不可变数据类型

import copy

a = [1, 3, 5, (7, 9)]
b = copy.copy(a)
c = copy.deepcopy(a)
print(id(a))  # 1186799997824
print(id(b))  # 1186799988352
print(id(c))  # 1186799988672

print(id(a[3]))  # 1700778811776
print(id(b[3]))  # 1700778811776
print(id(c[3]))  # 1700778811776

  • 外层对象是可变数据类型,所以可以进行完全拷贝(需要生成内存空间),但是内层对象是不可变数据类型,所以只能进行拷贝引用关系

案例2:不可变嵌套可变数据类型

d = (1, 3, 5, [7, 9])
e = copy.copy(d)
f = copy.deepcopy(d)

print(id(d))  # 2193468143712
print(id(e))  # 2193468143712
print(id(f))  # 2193468262576 内存地址不一样了

print(id(d[3]))  # 2764873530496
print(id(e[3]))  # 2764873530496
print(id(f[3]))  # 2764873530560
  • 如果一个不可变数据类型包含了可变数据类型,浅拷贝的结论与之前一致,都只能拷贝引用关系。但是对于深拷贝而言,这种数据类型整体都可以进行完全拷贝

image-20231020165800762