被一个及其简单的 a,b = b,a 问倒了,在线感性求助!!!

2018-07-22 19:42:56 +08:00
 firejoke

今天被问到
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)
a

1

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 大! 在线求助啊!!!

4699 次点击
所在节点    Python
22 条回复
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

一起等回复
qsnow6
2018-07-22 19:53:29 +08:00
数组不是一回事啊
qsnow6
2018-07-22 19:56:48 +08:00
每次()都创建一个新数组
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
求值规则是这么个流程,内部的具体实现我就不清楚了。
lance6716
2018-07-22 20:05:00 +08:00
Python dis 模块值得拥有
yelite
2018-07-22 20:12:21 +08:00
id((a,b)) 返回以后 (a,b) tuple 就因为引用计数为 0 被回收了
Zzdex
2018-07-22 20:53:44 +08:00
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.
lxy42
2018-07-22 21:59:52 +08:00
(a, b)每次都会生成新的 tuple 对象,ID 都是不一样的。id((a, b))调用结束后,(a, b)引用计数为 0 被回收。至于你 1 楼中的代码出现 id 一样的情况,应该是 Python 内存管理使用了回收的内存
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
jmc891205
2018-07-22 22:40:11 +08:00
你要演示的话应该到 cpython 的源码里打 log,而不是用 id
littlewey
2018-07-23 00:46:03 +08:00
@Zzdex 的答案正解,多谢,学习了。
copie
2018-07-23 07:18:06 +08:00
要计算一个变量的 id 的时候一定要确保这个变量不是被计算出来的。
简单来说就是这个变量一定是有人引用的。只有这样才可以算出来真正的 id。
c = (a,b)
d = (b,a)
这里 id(c) 就 不等于 id(d) 了。
会出现 id((a,b)) 等于 id((b,a)) 是因为引用计数为 0+内存被回收+缓存池 导致的
firejoke
2018-07-23 08:45:20 +08:00
@wangguoqin1001 我后来也这样试了,确实这样就会 id 不一样
@lance6716 哦~
firejoke
2018-07-23 09:02:22 +08:00
@lance6716 哦~ 还有这种东西?~


@yelite 前面被回收,后面又再次引用,所以导致出现了同样的 ID? 看后面 6、7、8、9 楼的回复好像是这样的, 万分感谢!!! 收好铜币哦


@Zzdex 大致明白了,我要去找这个库的文档了,但这个大写字母加下划线 →v→ ~ 万分感谢!!! 收好铜币哦


@lxy42 嗯, 把你的解释和 12 楼的结合起来就很清晰了, 还是内存机制导致的不确定, 万分感谢!!! 收好铜币哦

@copie 突然想到了 zen of python ,不要想一行代码解决问题 →_→, 万分感谢!!! 收好铜币哦
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。
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
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 内存管理的优化。
lilydjwg
2018-07-23 15:42:44 +08:00
@lxy42 #17 当然是优化啊。Python 从来没有说 id 不会被复用嘛。
lxy42
2018-07-23 16:14:20 +08:00
firejoke
2018-07-23 19:14:47 +08:00
@lilydjwg 三个的时候,倒没有创建元组
这个优化也是动态语言的一种个特点吧

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/473116

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX