变量机制
2020年5月26日
变量不是盒子,以及 深/浅拷贝、弱引用等
变量机制
1. 深浅拷贝
1.1 变量实现原理
a = 1
b = a
print(f"b: {b} - id: {id(b)}")
print(f"a: {a} - id: {id(a)}")
b = 2
print(f"b: {b} - id: {id(b)}")
## 输出结果
# b: 1 - id: 1938697316656
# a: 1 - id: 1938697316656
# b: 2 - id: 1938697316688
- 暂将 Python 中的变量理解为标签,首先,
a = 1
就是将不可变的整型 1 ,赋值给了a
,即:标签名为a
的变量,给内存中的整型对象 1 贴上了标签- 变量
a
存放了 1 的地址 (1938697316656) print(a)
时,a
中存放的地址指针,就会指向了(1938697316656)这块内存地址,并取出 1 这个值
- 变量
- 当然
b
也如此,b = a
就是又在整型对象 1 上,再贴个b
的标签- 此时变量
b
同a
,一样存放(1938697316656)
- 此时变量
- 然后
b = 2
则是将对象 1 的b
标签取了下来,转贴给了整型 2- 变量
b
存放了 2 的地址 (1938697316688)
- 变量
理解赋值,容易走入以下误区
- 误解 1,
a
、b
都有有自己的地址 - 误解 2,
b = a
是在b
中存放了a
的地址(误以为a
有地址),然后是通过b
指向->
a
指向->
1 得来 1 的值
提示
Python 的变量,其实是一种 堆内存的引用,更详细的需了解内存机制,因此
赋值的过程,就是 改变标签指向 的过程
参数传递的过程,就是 交换标签指向 的过程
“=” 左右的赋值原理如下
# 实际上是把等号右侧的先计算出来 a 是 1, 然后再将b 赋值 b = 1 a = 1 b = a # 两数交换也如此: c, d = 3, 4 c, d = d, c + d # 先将等式右侧计算出 4, 7, 然后左侧再赋值 c, d = 3, 7
1.2 浅拷贝
创建一个如下列表
a = [[1, 2], 3, 4]
print(f"所有地址: {id(a)}, {id(a[0])}, {id(a[1])}, {id(a[2])}, {id(a[0][0])}, {id(a[0][1])}")
## 所有地址: 1938767455680, 1938766586816, 1938697316720, 1938697316752, 1938697316656, 1938697316688
-----------------------------------------------------------------
print(f"查看地址: {id(a[1])} - {id(3)} and {a[1] is 3}")
## 查看地址: 1938697316720 - 1938697316720 and True
- 变量为
a
的列表,共开辟了 6 个内存地址空间,打上了a
标签,此时a
中存放了[[1, 2], 3, 4]
即 (1938767455680)[[1, 2], 3, 4]
中又囊括了[1, 2]
、3
、4
(1938766586816, 1938697316720, 1938697316752)三块地址[1, 2]
又囊括了 (1938697316656, 1938697316688)两块地址
此时对 a
,进行浅拷贝
b = a.copy()
print(f"b: {id(b)}, b is a: {b is a}")
## b: 1938766113856, b is a: False
----------------------------------------------------------
c = a
print(f"c: {id(c)}, c is a: {c is a}")
## c: 1938767455680, c is a: True
- 此时发现,直接赋值的
c
是和a
一模一样的存放了同一块地址,而变量b
,经过.copy()
而地址不同 - 那么此时变量
b
是独立的吗?
b[1] = 5
print(a)
## [[1, 2], 3, 4]
c[2] = 6
print(a)
## [[1, 2], 3, 6]
- 此时发现,
b
改变了列表中的不可变对象 3 (位置为1的元素)貌似没有影响到a
- 同时,未经过
.copy()
操作,而直接赋值的c
任意改动,都会影响a
中存放的值 - 那么变量
b
真的是独立的吗?
b[0][1] = 7
print(a)
## [[1, 7], 3, 6]
b
改变了列表中的可变对象 [1, 2] 里的元素,此时一下子影响到了a
- 一旦发生
b[0][1] = 7
这样的操作,实质上改变的是a[0]
里面的列表存放的地址[1938697316656, 1938697316688]
为[1938697316656, 1938697316848]
,此时a[0]
的地址,仍是未发生任何变化的
故如上拷贝操作,即是 浅拷贝,它只浅层拷贝各元素的单层地址 (第一层的地址指针)一旦存在可变对象,且变化,源也随之变化
提示
对于列表来说,.copy()
等同于 [:]
切片操作,即 b = a.copy()
is
b = a[:]
a = [1, 2, 3]
b = a[:] # a[:] 相当于重新copy, 即 a[0: -1],等同于 [1, 2, 3]
# 故等同于 重新赋值
b = [1, 2, 3]
1.3 深拷贝
通常情况,使用 浅拷贝 足矣,深拷贝 会实打实地拷贝了一份新的数据,完完全全地 开辟新的内存空间,这就十分消耗内存 了
文档
使用from copy import deepcopy
a = [[1, 2], 3, 4]
b = deepcopy(a)
b[0][1] = 5
print(f"a: {a} - b: {b}")
## 输出结果
# a: [[1, 2], 3, 4] - b: [[1, 5], 3, 4]
- 深拷贝
deepcopy()
会拷贝出一份,涉及到动态数据类型就地址完全不同的 对象赋值给变量b
- 此时变量
b
与变量a
毫不相干