被一个及其简单的 a,b = b,a 问倒了,在线感性求助!!! - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
firejoke
V2EX    Python

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

  •  
  •   firejoke 2018-07-22 19:42:56 +08:00 5127 次点击
    这是一个创建于 2641 天前的主题,其中的信息可能已经有所发展或是发生改变。

    今天被问到
    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 大! 在线求助啊!!!

    22 条回复    2018-07-24 22:04:25 +08:00
    wangguoqin1001
        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

    一起等回复
    qsnow6
        2
    qsnow6  
       2018-07-22 19:53:29 +08:00 via iPhone
    数组不是一回事啊
        3
    qsnow6  
       2018-07-22 19:56:48 +08:00 via iPhone
    每次()都创建一个新数组
    yunfeihe
        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
    求值规则是这么个流程,内部的具体实现我就不清楚了。
    lance6716
        5
    lance6716  
       2018-07-22 20:05:00 +08:00 via Android   2
    Python dis 模块值得拥有
    yelite
        6
    yelite  
       2018-07-22 20:12:21 +08:00   4
    id((a,b)) 返回以后 (a,b) tuple 就因为引用计数为 0 被回收了
    Zzdex
        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.
    lxy42
        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 内存管理使用了回收的内存
    lxy42
        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
    jmc891205
        10
    jmc891205  
       2018-07-22 22:40:11 +08:00
    你要演示的话应该到 cpython 的源码里打 log,而不是用 id
    littlewey
        11
    littlewey  
       2018-07-23 00:46:03 +08:00 via iPhone
    @Zzdex 的答案正解,多谢,学习了。
    copie
        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+内存被回收+缓存池 导致的
    firejoke
        13
    firejoke  
    OP
       2018-07-23 08:45:20 +08:00
    @wangguoqin1001 我后来也这样试了,确实这样就会 id 不一样
    @lance6716 哦~
    firejoke
        14
    firejoke  
    OP
       2018-07-23 09:02:22 +08:00
    @lance6716 哦~ 还有这种东西?~


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


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


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

    @copie 突然想到了 zen of python ,不要想一行代码解决问题 →_→, 万分感谢!!! 收好铜币哦
    dongdawang
        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。
    lilydjwg
        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
    lxy42
        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 内存管理的优化。
    lilydjwg
        18
    lilydjwg  
       2018-07-23 15:42:44 +08:00
    @lxy42 #17 当然是优化啊。Python 从来没有说 id 不会被复用嘛。
    firejoke
        20
    firejoke  
    OP
       2018-07-23 19:14:47 +08:00
    @lilydjwg 三个的时候,倒没有创建元组
    这个优化也是动态语言的一种个特点吧
    dongdawang
        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 来实现的?
    lilydjwg
        22
    lilydjwg  
       2018-07-24 22:04:25 +08:00
    @dongdawang #21 并没有构建 tuple 啊。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1044 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 18:34 PVG 02:34 LAX 11:34 JFK 14:34
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86