首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python 学习手册
Python Cookbook
Python 基础教程
Python Sites
PyPI - Python Package Index
http://www.simple-is-better.com/
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
V2EX  ›  Python

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

  •  
  •   firejoke · 215 天前 · 3313 次点击
    这是一个创建于 215 天前的主题,其中的信息可能已经有所发展或是发生改变。

    今天被问到
    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
        1
    wangguoqin1001   215 天前
    事到如今,只能怀疑是 id()在作妖了

    >>> a = 1
    >>> b = 2
    >>> id ((a,b))
    4526388432
    >>> id ((a,b))
    4526388504
    >>> id ((a,b))
    4526388432
    >>> id ((a,b))
    4526388504

    一起等回复
        2
    qsnow6   215 天前 via iPhone
    数组不是一回事啊
        3
    qsnow6   215 天前 via iPhone
    每次()都创建一个新数组
        4
    yunfeihe   215 天前
    右边的表达式先求值,再对等号求值;
    a = 1, b = 2
    b, a = a, b => b, a = 1, 2 => b = 1, a = 2
    求值规则是这么个流程,内部的具体实现我就不清楚了。
        5
    lance6716   215 天前 via Android   ♥ 2
    Python dis 模块值得拥有
        6
    yelite   215 天前   ♥ 4
    id((a,b)) 返回以后 (a,b) tuple 就因为引用计数为 0 被回收了
        7
    Zzdex   215 天前   ♥ 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   215 天前 via Android   ♥ 3
    (a, b)每次都会生成新的 tuple 对象,ID 都是不一样的。id((a, b))调用结束后,(a, b)引用计数为 0 被回收。至于你 1 楼中的代码出现 id 一样的情况,应该是 Python 内存管理使用了回收的内存
        9
    lxy42   215 天前
    下面是我在 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   215 天前
    你要演示的话应该到 cpython 的源码里打 log,而不是用 id
        11
    littlewey   215 天前 via iPhone
    @Zzdex 的答案正解,多谢,学习了。
        12
    copie   215 天前 via Android   ♥ 1
    要计算一个变量的 id 的时候一定要确保这个变量不是被计算出来的。
    简单来说就是这个变量一定是有人引用的。只有这样才可以算出来真正的 id。
    c = (a,b)
    d = (b,a)
    这里 id(c) 就 不等于 id(d) 了。
    会出现 id((a,b)) 等于 id((b,a)) 是因为引用计数为 0+内存被回收+缓存池 导致的
        13
    firejoke   215 天前
    @wangguoqin1001 我后来也这样试了,确实这样就会 id 不一样
    @lance6716 哦~
        14
    firejoke   215 天前
    @lance6716 哦~ 还有这种东西?~


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


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


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

    @copie 突然想到了 zen of python ,不要想一行代码解决问题 →_→, 万分感谢!!! 收好铜币哦
        15
    dongdawang   215 天前
    发现了一个有趣的现象,两个变量交换和四个变量交换使用的不是同一种方法。
    # 两个变量的交换
    >>> 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   215 天前
    噗,这是把自己给坑了呀。

    @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   215 天前
    @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 内存管理的优化。
        18
    lilydjwg   215 天前
    @lxy42 #17 当然是优化啊。Python 从来没有说 id 不会被复用嘛。
        20
    firejoke   214 天前
    @lilydjwg 三个的时候,倒没有创建元组
    这个优化也是动态语言的一种个特点吧
        21
    dongdawang   214 天前
    @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   213 天前
    @dongdawang #21 并没有构建 tuple 啊。
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2362 人在线   最高记录 4385   ·  
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 27ms · UTC 08:53 · PVG 16:53 · LAX 00:53 · JFK 03:53
    ♥ Do have faith in what you're doing.
    沪ICP备16043287号-1