乐趣区

关于python:Python可变与不可变数据深拷贝与浅拷贝

浅拷贝和深拷贝

拷贝函数是专门为可变数据类型 listsetdict 应用的一种函数。作用是,当一个值指向另一个值的时候,也不会影响指向的值,如果被指向的数据是可变数据,那么它一旦被批改,指向的数据也会随之扭转。

什么是可变数据和不可变数据

咱们来举一个例子,整型是不可变的数据,那么为什么是不可变的数据呢?一个数据是不是可变的就要关系到 python 的缓存机制。

当一个数据发生变化,如果它的内存地址没有发生变化,就阐明这是一个可变数据。

比如说,咱们当初创立一个值是 a 的变量,它的值是 100,而后让这个数值发生变化,观察者个变量的内存地址是否产生了变动。

a = 100
print(a, id(a)) # 100 1610845392

a += 100
print(a, id(a)) # 200 1610848592

咱们发现数值产生了变动,变量的内存也跟着产生了变动,咱们再创立一个变量 b,值也是整型 100

b = 100
print(b, id(b))    # 100 1610845392

发现 b 的内存地址和 a 的内存地址是一样的,也就是说,像整型这样的数据类型,一个数字就独占一个内存地址,当某个指向这个值的变量,产生了变动的时候,不是这个变量的值要扭转,而是这个变量要寻找扭转后的值的内存地址,而后从新的指向它。只有你的硬件不重新启动,那么这个内存地址就永远也不会发生变化了,这样的数据就是不可变数据。

那么,反之就是可变数据,指的就是当变量指向的值发生变化之后,在这个内存地址上的值实打实的发生变化的值,就是可变数据类型。

比方列表,列表产生扭转之后,是在原有的根底上发生变化的,所以内存地址是不会扭转的,这就是可变数据类型,可变数据类型没有内存缓存机制,不能节俭内存,所以截然不同的数据,他们的内存地址可能是不雷同的。

a = [1, 2]
print(a, id(a)) # [1, 2] 1528536069896

a.append(3)
print(a, id(a)) # [1, 2, 3] 1528536069896

# b 和 a 的值雷同,然而内存地址不雷同
b = [1, 2, 3]
print(b, id(b)) # [1, 2, 3] 1528536069832

那么拷贝函数是干什么的?

在咱们的理论工作当中,常常会应用的一种操作就是定义一个变量,它的值间接就赋给了一个原有的变量之上。可是变量定义之后咱们绝不是用来作为一个陈设的,而是要做运算、或者是做一个长期的存储,那么原有的变量的值是要扭转的,问题就来了,如果是一个不可变的数据还好,如果是可变的数据,间接的赋值他们的内存地址是雷同的,如果一个变量的值发生变化,同内存地址的的值就都产生扭转了,咱们的向要长期存储的值也就不再是咱们想要的那个值了,这是绝大多数的时候咱们不想看到的后果。

咱们拿整型为例,变量 a 间接赋值给变量 b,这个时候的变量 a b 的值是雷同的,然而如果变量 a 的值产生了变动,是丝毫不影响变量 b 的值的。

a = 100
print(a, id(a))  # 100 1610845392

b = a
print(b, id(b))  # 100 1610845392

a += 100
print(a, id(a))  # 200 1610848592
print(b, id(b))  # 100 1610845392

然而如果是可变数据就不是这样的状况了

a = [1, 2]
print(a, id(a))  # [1, 2] 2077688035080

b = a
print(b, id(b))  # [1, 2] 2077688035080

a.append(3)
print(a, id(a))  # [1, 2, 3] 2077688035080
print(b, id(b))  # [1, 2, 3] 2077688035080

不可变数据的这个个性既是一个长处也是一个毛病,毛病就是如果咱们想要保留 a 变量发生变化之前的的一个情况的时候,是保留不下来的,这个时候就呈现了拷贝函数,它能够将可变数据变成不可变数据那样的成果。

浅拷贝

应用拷贝函数,将 a 变量放入作为参数放入函数中,应用 b 变量承受函数的返回值,就胜利的拷贝了变量 a,变量 b 的内存地址和变量 a 的不一样,这样当它们其中一方发生变化之后,不会影响到另一方的数据。

# 拷贝函数不能间接应用,须要应用 import 导入 copy 模块,copy 模块的 copy 函数就是浅拷贝

import copy

a = [1, 2, 3]

# 变量 b 不在间接是变量 a 的间接赋值了,而是通过 copy 函数的返回值
b = copy.copy(a)

# 他们的数值一样,然而内存地址不同,所以他们之间的任意一方发生变化都不会影响到第二方。print(a, id(a))  # [1, 2, 3] 2343743813320
print(b, id(b))  # [1, 2, 3] 2343743813192

a.append(4)
print(a, id(a))  # [1, 2, 3, 4] 2343743813320
print(b, id(b))  # [1, 2, 3] 2343743813192

然而如果变量 a 是一个二级容器或者是一个更多级容器,浅拷贝无奈拷贝第二级容器或者更多级的容器,所以当第二级容器或者是更多级的容器发生变化的时候,还是会发生变化,因为浅拷贝只能拷贝一级容器,所以多级容器的内存地址还是雷同的。

import copy

a = [[66,88], 2, 3]

b = copy.copy(a)

print(a, id(a))  # [[66, 88], 2, 3] 2431683163720
print(b, id(b))  # [[66, 88], 2, 3] 2431683162184

# 扭转二级容器
a[0].append(100)
print(a, id(a))  # [[66, 88, 100], 2, 3] 2431683163720
print(b, id(b))  # [[66, 88, 100], 2, 3] 2431683162184

# 浅拷贝不能拷贝二级及以上的容器
print(id(a[0]))  # 1582481372872
print(id(b[0]))  # 1582481372872

深拷贝

浅拷贝只能拷贝一级容器

所以诞生了深拷贝,深拷贝能够拷贝所有级别的容器。

import copy

a = [[66,88], 2, 3]

# 深拷贝应用 deepcopy 函数
b = copy.deepcopy(a)


print(a, id(a))  # [[66, 88], 2, 3] 2168411158088
print(b, id(b))  # [[66, 88], 2, 3] 2168411156552

a[0].append(100)
print(a, id(a))  # [[66, 88, 100], 2, 3] 2168411158088
print(b, id(b))  # [[66, 88], 2, 3] 2168411156552

# 深拷贝所有级别的容器
print(id(a[0]))  # 2168411158216
print(id(b[0]))  # 2168411122760

总结

应用深浅拷贝须要导入 copy 模块;

浅拷贝应用 copy 函数,只能拷贝一级容器的所有元素;

深拷贝应用 deepcopy 函数,能够拷贝所有级别容器的所有元素;

规范库 copy 中只有 copydeepcopy两个函数对外开放应用;

因为深拷贝要拷贝的元素跟多,所以速度会远不如浅拷贝,在编程的过程中要留神防止造成多余的零碎累赘;

python 中的不可变数据是 Number、string、tuple,可变数据是 list、set、dict;而拷贝就是专门为可变数据提供的,所以深浅拷贝只实用于 list、set、dict,当然,可变数据应用拷贝函数也不会出错,然而没有意义。

退出移动版