变量机制

五十岚2020年5月26日
大约 4 分钟

变量不是盒子,以及 深/浅拷贝、弱引用等

变量机制

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 的标签
    • 此时变量 ba,一样存放(1938697316656
  • 然后 b = 2 则是将对象 1b 标签取了下来,转贴给了整型 2
    • 变量 b 存放了 2 的地址 (1938697316688

理解赋值,容易走入以下误区

  • 误解 1ab 都有有自己的地址
  • 误解 2b = a 是在 b 中存放了 a 的地址(误以为 a 有地址),然后是通过 b 指向-> a 指向-> 1 得来 1 的值

提示

Python 的变量,其实是一种 堆内存的引用,更详细的需了解内存机制,因此

  1. 赋值的过程,就是 改变标签指向 的过程

  2. 参数传递的过程,就是 交换标签指向 的过程

  3. “=” 左右的赋值原理如下

    # 实际上是把等号右侧的先计算出来 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]341938766586816, 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 深拷贝

通常情况,使用 浅拷贝 足矣,深拷贝 会实打实地拷贝了一份新的数据,完完全全地 开辟新的内存空间,这就十分消耗内存

使用 文档open in new window
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 毫不相干
上次编辑于: 2023/6/11 05:01:15