Python中 a,b = b,a 的交换原理是什么?在线求助!
今天被问到
a,b = b,a 是如何实现的
轻蔑的告诉对方这是因为交换了内存地址啊
然而我自己多事,要给别人演示
a = 1
b = 2
id(a)4304968096
id(b)
4304968064
a,b = b,a
id(a)4304968064
id(b)
4304968096
目前为止没有任何问题
然后我又解释到:
因为实际上它是这样运行的啊
(a,b)=(b,a)
a1
b
2
你看,这是生成了两个新的元组在参与运算
id((a,b)),id((b,a))
(4356155464, 4356155464)
诶?! 你等等
(a,b) is (b,a)
False
诶?! 诶?! 你再等等(莫非 tuple 太特殊了)
id([a,b]),id([b,a])
(4356157384, 4356157384)
我 c?! 不行!
id([a,b][0]),id([b,a][0])
(4304968096, 4304968064)
是不同的 id 啊,这个......
那个,你等等啊
各位 V 大! 在线求助啊!!!
Python中 a,b = b,a 的交换原理是什么?在线求助!
事到如今,只能怀疑是 id()在作妖了
>>> a = 1
>>> b = 2
>>> id ((a,b))
4526388432
>>> id ((a,b))
4526388504
>>> id ((a,b))
4526388432
>>> id ((a,b))
4526388504
一起等回复
这是Python的并行赋值特性,底层通过创建临时元组实现交换。
# 实际执行过程相当于:
a = 1
b = 2
_temp = (b, a) # 创建临时元组(2, 1)
a = _temp[0] # a = 2
b = _temp[1] # b = 1
关键点:
- 右侧表达式
b, a先被求值为元组(b的当前值, a的当前值) - 左侧变量按位置接收元组中的值
- 整个过程是原子的,不需要中间变量
这比传统三变量交换更简洁高效,因为Python解释器能直接优化这个操作。
一句话总结:利用元组打包和解包的原子操作实现变量交换。
数组不是一回事啊
右边的表达式先求值,再对等号求值;
a = 1, b = 2
b, a = a, b => b, a = 1, 2 => b = 1, a = 2
求值规则是这么个流程,内部的具体实现我就不清楚了。
Python dis 模块值得拥有
id((a,b)) 返回以后 (a,b) tuple 就因为引用计数为 0 被回收了
(a, b)每次都会生成新的 tuple 对象,ID 都是不一样的。id((a, b))调用结束后,(a, b)引用计数为 0 被回收。至于你 1 楼中的代码出现 id 一样的情况,应该是 Python 内存管理使用了回收的内存
下面是我在 IPython 中测试的结果:
In [1]: a = 1
In [2]: b = 2
In [3]: id((a, b))
Out[3]: 4512224320
In [4]: id((a, b))
Out[4]: 4510990992
In [5]: id((a, b))
Out[5]: 4510066880
In [6]: id((a, b))
Out[6]: 4510871064
In [10]: t1 = (a, b)
In [11]: t2 = (a, b)
In [12]: t3 = (a, b)
In [13]: t4 = (a, b)
In [14]: id(t1)
Out[14]: 4512123720
In [15]: id(t2)
Out[15]: 4511225328
In [16]: id(t3)
Out[16]: 4510011976
In [17]: id(t4)
Out[17]: 4510782816
你要演示的话应该到 cpython 的源码里打 log,而不是用 id
的答案正解,多谢,学习了。
要计算一个变量的 id 的时候一定要确保这个变量不是被计算出来的。
简单来说就是这个变量一定是有人引用的。只有这样才可以算出来真正的 id。
c = (a,b)
d = (b,a)
这里 id© 就 不等于 id(d) 了。
会出现 id((a,b)) 等于 id((b,a)) 是因为引用计数为 0+内存被回收+缓存池 导致的
我后来也这样试了,确实这样就会 id 不一样
哦~
哦~ 还有这种东西?~
前面被回收,后面又再次引用,所以导致出现了同样的 ID? 看后面 6、7、8、9 楼的回复好像是这样的, 万分感谢!!! 收好铜币哦
大致明白了,我要去找这个库的文档了,但这个大写字母加下划线 →v→ ~ 万分感谢!!! 收好铜币哦
嗯, 把你的解释和 12 楼的结合起来就很清晰了, 还是内存机制导致的不确定, 万分感谢!!! 收好铜币哦
突然想到了 zen of python ,不要想一行代码解决问题 →_→, 万分感谢!!! 收好铜币哦
发现了一个有趣的现象,两个变量交换和四个变量交换使用的不是同一种方法。
# 两个变量的交换
>>> dis.dis(“a=100;b=1000;a,b=b,a”)
1 0 LOAD_CONST 0 (100)
3 STORE_NAME 0 (a)
6 LOAD_CONST 1 (1000)
9 STORE_NAME 1 (b)
12 LOAD_NAME 1 (b)
15 LOAD_NAME 0 (a)
18 ROT_TWO
19 STORE_NAME 0 (a)
22 STORE_NAME 1 (b)
25 LOAD_CONST 2 (None)
28 RETURN_VALUE
# 四个变量的交换
>>> dis.dis(“a=100;b=1000;c=10000;d=10000;a,c,d,b=b,a,c,d”)
1 0 LOAD_CONST 0 (100)
3 STORE_NAME 0 (a)
6 LOAD_CONST 1 (1000)
9 STORE_NAME 1 (b)
12 LOAD_CONST 2 (10000)
15 STORE_NAME 2 ©
18 LOAD_CONST 2 (10000)
21 STORE_NAME 3 (d)
24 LOAD_NAME 1 (b)
27 LOAD_NAME 0 (a)
30 LOAD_NAME 2 ©
33 LOAD_NAME 3 (d)
36 BUILD_TUPLE 4
39 UNPACK_SEQUENCE 4
42 STORE_NAME 0 (a)
45 STORE_NAME 2 ©
48 STORE_NAME 3 (d)
51 STORE_NAME 1 (b)
54 LOAD_CONST 3 (None)
57 RETURN_VALUE
###
两个变量交换的时候,python 没有构建 tuple,但是四个变量交换的时候,python 构建了 tuple。
噗,这是把自己给坑了呀。
#15 那么一个很自然的问题是,三个的情况呢?
#9 IPython 里很多行为不一样的,因为中间多了一层。
我这里的结果很有意思,有几个 tuple 间隔地被重复使用:
>>> a = 1
>>> b = 2
>>> id((a, b))
140111655945608
>>> id((a, b))
140111675289544
>>> id((a, b))
140111655945608
>>> id((a, b))
140111675289544
>>> id((a, b)), id((b, a))
(140111675289544, 140111675289544)
>>> id((a, b))
140111655841608
>>> id((a, b)), id((b, a))
(140111655992648, 140111655992648)
>>> id((a, b))
140111655945608
>>> id((a, b)), id((b, a))
(140111675289544, 140111675289544)
>>> id((a, b)), id((b, a))
(140111655841608, 140111655841608)
>>> id((a, b)), id((b, a))
(140111655945608, 140111655945608)
>>> id((a, b)), id((b, a))
(140111675289544, 140111675289544)
Python 3.6.6
#16 我刚在 Python ( 2.7.10 )的解释器测试,得到的结果和你的类似,tuple 的内存被交替使用。
>>> a = 1
>>> b = 2
>>> id((a, b))
4469425216
>>> id((a, b))
4469425504
>>> id((a, b))
4469425216
>>> id((a, b))
4469425504
>>> id((a, b))
4469425216
>>> id((a, b)), id((b, a))
(4469425504, 4469425504)
>>> id((a, b))
4469425432
>>> id((a, b)), id((b, a))
(4469425432, 4469425432)
然后我进一步测试:
>>> a = 1
>>> b = 2
>>> c = 3
>>> id((a, b, c))
4403338528
>>> id((a, b, c))
4403338528
>>> id((a, b, c))
4403338528
>>> x = (a, b, c)
>>> id(x)
4403338528
>>> id(x)
4403338528
>>> del x
>>> id((a, b, c))
4403338528
我觉得这是 Python 内存管理的优化。
#17 当然是优化啊。Python 从来没有说 id 不会被复用嘛。
三个的时候,倒没有创建元组
这个优化也是动态语言的一种个特点吧
三个变量的交换
import dis
dis.dis(“a=1;b=2;c=3;a,c,b=c,b,a”)
1 0 LOAD_CONST 0 (1)
3 STORE_NAME 0 (a)
6 LOAD_CONST 1 (2)
9 STORE_NAME 1 (b)
12 LOAD_CONST 2 (3)
15 STORE_NAME 2 ©
18 LOAD_NAME 2 ©
21 LOAD_NAME 1 (b)
24 LOAD_NAME 0 (a)
27 ROT_THREE
28 ROT_TWO
29 STORE_NAME 0 (a)
32 STORE_NAME 2 ©
35 STORE_NAME 1 (b)
38 LOAD_CONST 3 (None)
41 RETURN_VALUE
#三个变量的交换使用 ROT_THREE。
但是发现
无论是
dis.dis(“a=1;b=2;(a,b)=(b,a)”)
还是
dis.dis(“a=1;b=2;a,b=b,a”)
字节码命令都是
1 0 LOAD_CONST 0 (1)
3 STORE_NAME 0 (a)
6 LOAD_CONST 1 (2)
9 STORE_NAME 1 (b)
12 LOAD_NAME 1 (b)
15 LOAD_NAME 0 (a)
18 ROT_TWO
19 STORE_NAME 0 (a)
22 STORE_NAME 1 (b)
25 LOAD_CONST 2 (None)
28 RETURN_VALUE
也就是说无论几个元素交换,都是构建 tuple 来实现的?
#21 并没有构建 tuple 啊。


