关于python:天啦噜仅仅5张图彻底搞懂Python中的深浅拷贝

17次阅读

共计 2553 个字符,预计需要花费 7 分钟才能阅读完成。

Python 中的深浅拷贝

在讲深浅拷贝之前,咱们先重温一下 is== 的区别。

在判断对象是否相等比拟的时候咱们能够用is==

  • is:比拟两个对象的援用是否雷同,即 它们的 id 是否一样
  • ==:比拟两个对象的值是否雷同。

id(),是 Python 的一个内置函数,返回对象的惟一标识,用于获取对象的内存地址。

如下

首先,会为整数 1 调配一个内存空间。变量 a 和 b 都指向了这个内存空间(内存地址相等),所以他们的 id 相等。

a is bTrue

然而,真的所有整数数字都这样吗?答案是:不是!只有在 -25 ~ 256 范畴中的整数才不会重新分配内存空间。

如下所示:

因为 257 超出了范畴,所以 id 不雷同,所以 a is b 返回的值为 False。

>>> a = 257
>>> b = 257
>>> print(id(a))
20004752
>>> print(id(b))
20001312
>>> print(a is b)
False
>>> print(a == b)
True

这样做是思考到性能,Python 对 -5 到 256 的整数保护了一个数组,相当于一个缓存,当数值在这个范畴内,间接就从数组中返回绝对应的援用地址了。如果不在这个范畴内,会从新开拓一个新的内存空间。

is 和 == 哪个效率高?

相比之下,is比拟的效率更高,因为它只须要判断两个对象的 id 是否雷同即可。

== 则须要重载__eq__ 这个函数,遍历变量中的所有元素内容,逐次比拟是否雷同。因而效率较低

浅拷贝 深拷贝

给变量进行赋值,有两种办法 间接赋值,拷贝

间接赋值就 = 就能够了。而拷贝又分为浅拷贝和深拷贝

先说论断吧:

  • 浅拷贝:拷贝的是对象的援用,如果原对象扭转,相应的拷贝对象也会产生扭转
  • 深拷贝:拷贝对象中的每个元素,拷贝对象和原有对象不在有关系,两个是独立的对象

光看下面的概念,对老手来讲可能不太好了解。来看上面的例子吧

赋值

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

定义变量 a,同时将 a 赋值给 b。打印之后发现他们的 id 是雷同的。阐明指向了同一个内存地址。

而后批改 a 的值,再查看他们的 id

a = [1, 2, 3]
b = a
print(id(a))  # 46169960
a[1] = 0
print(a, b)  # [1, 0, 3] [1, 0, 3]
print(id(a))  # 46169960
print(id(b))  # 46169960

这时候发现批改后的 a 和 b 以及最开始的 a 的内存地址是一样的。也就是说 a 和 b 还是指向了那一块内存,只不过内存外面的[1, 2, 3] 变成了[1, 0, 3]

因为每次从新执行的时候内存地址都是产生扭转的,此时的 id(a) 的值 46169960 与 52531048 是一样的

所以咱们就能够判断出,b 和 a 的援用是雷同的,当 a 产生扭转的时候,b 也会产生扭转。

赋值就是:你 a 无论怎么变,你指向谁,我 b 就跟着你指向谁。

拷贝

提到拷贝就防止不了可变对象和不可变对象。

  • 可变对象:当有须要扭转对象外部的值的时候,这个对象的 id 不发生变化。
  • 不可变对象:当有须要扭转对象外部的值的时候,这个对象的 id 会发生变化。
a = [1, 2, 3]
print(id(a)) # 56082504
a.append(4)
# 批改列表 a 之后 id 没产生扭转,可变对象
print(id(a)) # 56082504

a = 'hello'
print(id(a)) # 59817760
a = a + 'world'
print(id(a)) # 57880072
# 批改字符串 a 之后,id 产生了变动。不可变对象
print(a) # hello world

浅拷贝

拷贝的是不可变对象,肯定水平上来讲等同于赋值操作。然而对于多层嵌套构造,浅拷贝只拷贝父对象,不拷贝外部的子对象。

应用 copy 模块的 copy.copy 进行浅拷贝。

import copy
a = [1, 2, 3]
b = copy.copy(a)
print(id(a))  # 55755880
print(id(b))  # 55737992
a[1] = 0
print(a, b) # [1, 0, 3] [1, 2, 3]

艰深的讲,我将当初的 a 复制一份重新分配了一个内存空间。前面你 a 怎么扭转,那跟我 b 是没有任何关系的。

对于列表的浅拷贝还能够通过 list(),list[:] 来实现

然而!我后面提到了对于多层嵌套的构造,须要留神

看上面的例子

import copy
a = [1, 2, [3, 4]]
b = copy.copy(a)

print(id(a)) # 23967528
print(id(b)) # 21738984
# 扭转 a 中的子列表
a[-1].append(5)
print(a) # [1, 2, [3, 4, 5]]
print(b) # [1, 2, [3, 4, 5]]??为什么不是 [1, 2, [3, 4]] 呢?

b 是由 a 浅拷贝失去的。我批改了 a 中嵌套的列表,发现 b 也跟着批改了?

如果还是不太了解,能够参考下图。LIST 就是一个嵌套的子对象,指向了另外一个内存空间。所以浅拷贝只是拷贝了元素12 和子对象的援用!

另外一种状况,如果嵌套的是一个元组呢?

import copy
a = [1, 2, (3, 4)]
b = copy.copy(a)

# 扭转 a 中的元组
a[-1] += (5,)
print(a) # [1, 2, (3, 4, 5)]
print(b) # [1, 2, (3, 4)]

咱们发现浅拷贝得来的 b 并没有产生扭转。因为元组是不可变对象。扭转了元组就会生成新的对象。b 中的元组援用还是指向了旧的元组。

深拷贝

所谓深拷贝呢,就是重新分配一个内存空间(新对象),将原对象中的所有元素通过递归的形式进行拷贝到新对象中。

在 Python 中 通过 copy.deepcopy() 来实现深拷贝。

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

print(id(a)) # 66587176
print(id(b)) # 66587688
# 扭转 a 中的可变对象
a[-1].append(5)
print(a) # [1, 2, [3, 4, 5]]
print(b) # [1, 2, [3, 4]]  深拷贝之后字列表不会受原来的影响

结语

1、深浅拷贝都会对源对象进行复制,占用不同的内存空间

2、如果源对象没有子目录,则浅拷贝只能拷贝父目录,改变子目录时会影响浅拷贝的对象

3、列表的切片实质就是浅拷贝


史上最全 Python 材料汇总(长期更新)。隔壁小孩都馋哭了 — 点击支付

正文完
 0