今天被问到
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 大! 在线求助啊!!!
1
wangguoqin1001 2018-07-22 19:52:52 +08:00
事到如今,只能怀疑是 id()在作妖了
>>> a = 1 >>> b = 2 >>> id ((a,b)) 4526388432 >>> id ((a,b)) 4526388504 >>> id ((a,b)) 4526388432 >>> id ((a,b)) 4526388504 一起等回复 |
2
qsnow6 2018-07-22 19:53:29 +08:00 via iPhone
数组不是一回事啊
|
3
qsnow6 2018-07-22 19:56:48 +08:00 via iPhone
每次()都创建一个新数组
|
4
yunfeihe 2018-07-22 20:01:05 +08:00
右边的表达式先求值,再对等号求值;
a = 1, b = 2 b, a = a, b => b, a = 1, 2 => b = 1, a = 2 求值规则是这么个流程,内部的具体实现我就不清楚了。 |
5
lance6716 2018-07-22 20:05:00 +08:00 via Android 2
Python dis 模块值得拥有
|
6
yelite 2018-07-22 20:12:21 +08:00 4
id((a,b)) 返回以后 (a,b) tuple 就因为引用计数为 0 被回收了
|
7
Zzdex 2018-07-22 20:53:44 +08:00 4
dis.dis(compile("a = 0; b = 1; a,b = b,a", "<string>", "exec"))
0 LOAD_CONST 0 (0) 2 STORE_NAME 0 (a) 4 LOAD_CONST 1 (1) 6 STORE_NAME 1 (b) 8 LOAD_NAME 1 (b) 10 LOAD_NAME 0 (a) 12 ROT_TWO 官方文档的解释 ROT_TWO() Swaps the two top-most stack items. |
8
lxy42 2018-07-22 21:59:52 +08:00 via Android 3
(a, b)每次都会生成新的 tuple 对象,ID 都是不一样的。id((a, b))调用结束后,(a, b)引用计数为 0 被回收。至于你 1 楼中的代码出现 id 一样的情况,应该是 Python 内存管理使用了回收的内存
|
9
lxy42 2018-07-22 22:01:21 +08:00
下面是我在 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 |
10
jmc891205 2018-07-22 22:40:11 +08:00
你要演示的话应该到 cpython 的源码里打 log,而不是用 id
|
12
copie 2018-07-23 07:18:06 +08:00 via Android 1
要计算一个变量的 id 的时候一定要确保这个变量不是被计算出来的。
简单来说就是这个变量一定是有人引用的。只有这样才可以算出来真正的 id。 c = (a,b) d = (b,a) 这里 id(c) 就 不等于 id(d) 了。 会出现 id((a,b)) 等于 id((b,a)) 是因为引用计数为 0+内存被回收+缓存池 导致的 |
13
firejoke OP |
14
firejoke OP |
15
dongdawang 2018-07-23 14:15:21 +08:00
发现了一个有趣的现象,两个变量交换和四个变量交换使用的不是同一种方法。
# 两个变量的交换 >>> 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 (c) 18 LOAD_CONST 2 (10000) 21 STORE_NAME 3 (d) 24 LOAD_NAME 1 (b) 27 LOAD_NAME 0 (a) 30 LOAD_NAME 2 (c) 33 LOAD_NAME 3 (d) 36 BUILD_TUPLE 4 39 UNPACK_SEQUENCE 4 42 STORE_NAME 0 (a) 45 STORE_NAME 2 (c) 48 STORE_NAME 3 (d) 51 STORE_NAME 1 (b) 54 LOAD_CONST 3 (None) 57 RETURN_VALUE ### 两个变量交换的时候,python 没有构建 tuple,但是四个变量交换的时候,python 构建了 tuple。 |
16
lilydjwg 2018-07-23 15:22:33 +08:00
噗,这是把自己给坑了呀。
@dongdawang #15 那么一个很自然的问题是,三个的情况呢? @lxy42 #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 |
17
lxy42 2018-07-23 15:41:20 +08:00
@lilydjwg #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 内存管理的优化。 |
19
lxy42 2018-07-23 16:14:20 +08:00 1
|
21
dongdawang 2018-07-24 11:59:24 +08:00
@lilydjwg
三个变量的交换 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 (c) 18 LOAD_NAME 2 (c) 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 (c) 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 来实现的? |
22
lilydjwg 2018-07-24 22:04:25 +08:00
@dongdawang #21 并没有构建 tuple 啊。
|