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 · 4704 次点击
    这是一个创建于 2321 天前的主题,其中的信息可能已经有所发展或是发生改变。

    今天被问到
    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
    数组不是一回事啊
    qsnow6
        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   ·   实用小工具   ·   2187 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 21ms · UTC 16:14 · PVG 00:14 · LAX 08:14 · JFK 11:14
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.