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
lzjun
V2EX  ›  Python

Python 表达式 i += x 与 i = i + x 等价吗?

  •  
  •   lzjun ·
    lzjun567 · 2017-02-05 13:22:45 +08:00 · 6743 次点击
    这是一个创建于 2851 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Python 表达式 i += xi = i + x 等价吗?如果你的回答是 yes ,那么恭喜你正确了 50%,为什么说只对了一半呢? 按照我们的一般理解它们俩是等价的,整数操作时两者没什么异同,但是对于列表操作,是不是也一样呢?先看下面两段代码:

    代码 1

    >>> l1 = range(3)
    >>> l2 = l1
    >>> l2 += [3]
    >>> l1
    [0, 1, 2, 3]
    >>> l2
    [0, 1, 2, 3]
    

    代码 2

    >>> l1 = range(3)
    >>> l2 = l1
    >>> l2 = l2 + [3]
    >>> l1
    [0, 1, 2]
    >>> l2
    [0, 1, 2, 3]
    

    代码 1 与代码 2 中的l2的值是一样的,但是l1的值却不一样,说明 i += xi = i + x 是不等价的,那什么情况下等价,什么情况下不等价呢?

    弄清楚这个问题之前,首选得明白两个概念:可变对象( mutable )与不可变对象( immutable )。在 Python 中任何对象都有的三个通用属性:唯一标识、类型、值。

    唯一标识:用于标识对象的在内存中唯一性,它在对象创建之后就不会再改变,函数 id()可以查看对象的唯一标识

    类型:决定了该对象支持哪些操作,不同类型的对象支持的操作就不一样,比如列表可以有 length 属性,而整数没有。同样地对象的类型一旦确定了就不会再变,函数 type()可以返回对象的类型信息。

    对象的与唯一标识不一样,并不是所有的对象的值都是一成不变的,有些对象的值可以通过某些操作发生改变,值可以变化的对象称之为可变对象( mutable ),值不能改变的对象称之为不可变对象( immutable )

    不可变对象( immutable )

    对于不可变对象,值永远是刚开始创建时候的值,对该对象做的任何操作都会导致一个新的对象的创建。

    >>> a = 1
    >>> id(a)
    32574568
    >>> a += 1
    >>> id(a)
    32574544
    

    整数 “ 1 ” 是一个不可变对象,最初赋值的时候,a 指向的是整数对象 1 ,但对变量 a 执行 += 操作后, a 指向另外一个整数对象 2 ,但对象 1 还是在那里没有发生任何变化,而 变量 a 已经指向了一个新的对象 2 ,常见的不可变对象有: int 、 tuple 、 set 、 str 。

    imutable.png

    可变对象( mutable )

    可变对象的值可以通过某些操作动态的改变,比如列表对象,可以通过 append 方法不断地往列表中添加元素,该列表的值就在不断的处于变化中,一个可变对象赋值给两个变量时,他们共享同一个实例对象,指向相同的内存地址,对其中任何一个变量操作时,同时也会影响另外一个变量。

    >>> x = range(3)
    >>> y = x
    
    >>> id(x)
    139726103041232
    >>> id(y)
    139726103041232
    
    >>> x.append(3)
    >>> x
    [0, 1, 2, 3]
    >>> y
    [0, 1, 2, 3]
    
    >>> id(x)
    139726103041232
    >>> id(y)
    139726103041232
    

    imutable1.png

    执行 append 操作后,对象的内存地址不会改变, x 、 y 依然指向的是原来同一个对象,只不过是他的值发生了变化而已。

    imutable2.png

    理解完可变对象与不可变对象后,回到问题本身,+=+的区别在哪里呢?

    += 操作首先会尝试调用对象的 __iadd__方法,如果没有该方法,那么尝试调用__add__方法,先来看看这两个方法有什么区别

    __add__ 和 __iadd__ 的区别

    • __add__ 方法接收两个参数,返回它们的和,两个参数的值均不会改变。
    • __iadd__ 方法同样接收两个参数,但它是属于 in-place 操作,就是说它会改变第一个参数的值,因为这需要对象是可变的,所以对于不可变对象没有__iadd__方法。
    >>> hasattr(int, '__iadd__')
    False
    >>> hasattr(list, '__iadd__')
    True
    

    显然,整数对象是没有__iadd__的,而列表对象提供了__iadd__方法。

    >>> l2 += [3]  # 使用__iadd__, l2 的值原地修改
    

    代码 1 中的 += 操作调用的是__iadd__方法,他会原地修改 l2 指向的那个对象本身的值 imutable3.png

    >>> l2 = l2 + [3]  # 调用 __add__,创建了一个新的列表,赋值给了 l2
    

    而代码 2 中的 + 操作调用的是 __add__ 方法,该方法会返回一个新的对象,原来的对象保持不变, l1 还是指向原来的对象,而 l2 已经指向一个新的对象。

    imutable4.png

    以上就是表达式 i += x 与 i = i + x 的区别。因此对于列表进行 += 操作时,会存在潜在的 bug ,因为 l1 会因为 l2 的变化而发生改变,就像函数的参数不宜使用可变对象作为关键字参数一样。

    关注公众号 一个程序员的微站(VTtalk) 分享 Python 干货和有温度的内容

    weixin

    第 1 条附言  ·  2017-02-06 14:21:12 +08:00
    不好意思,标题误导了大家,但是内容绝对没有胡说八道,请明察。那些一味冷嘲热讽的同学可以点『忽略主题』
    24 条回复    2017-02-09 01:16:24 +08:00
    huluhulu
        1
    huluhulu  
       2017-02-05 14:06:16 +08:00   ❤️ 1
    当看到这么长的内容之后,我确定一定以及肯定最后是推广。。。
    拉到最下面果然是推广。。。
    Cbdy
        2
    Cbdy  
       2017-02-05 14:19:25 +08:00 via Android
    运算符重载。。
    lomoblur
        3
    lomoblur  
       2017-02-05 14:41:09 +08:00 via iPhone
    @huluhulu 我也是,看完前几句感觉语气不对直接拉到最底下
    lwjcjmx123
        4
    lwjcjmx123  
       2017-02-05 15:06:50 +08:00 via Android
    不知道为嘛,看到开头我就直接翻到最底下。真的是直觉,看来和楼上的几位一样
    Kilerd
        5
    Kilerd  
       2017-02-05 15:08:23 +08:00 via iPhone
    扯淡东西。

    重载后,我还能说你答对了 0%呢
    fffflyfish
        6
    fffflyfish  
       2017-02-05 15:12:29 +08:00   ❤️ 1
    我觉得题目应该改成 深拷贝浅拷贝 或者值传递地址传递类似的说法,这个标题有标题党的嫌疑
    crab
        7
    crab  
       2017-02-05 15:15:09 +08:00
    这和表达式写法不同无关吧?
    danielmiao
        8
    danielmiao  
       2017-02-05 15:20:54 +08:00
    现在的题目已经不为检验技术能力工作了,纯粹为了考验而考验,脱离了实际运用。。。
    keisuu
        9
    keisuu  
       2017-02-05 16:00:38 +08:00
    牛。测试了,果然如此。以后要少用 i += x ,尤其时列表操作
    phrack
        10
    phrack  
       2017-02-05 18:18:08 +08:00 via Android
    我 tm 为什么要花时间看这 xx 乱扯?
    deleted
        11
    deleted  
       2017-02-05 18:43:19 +08:00 via Android
    学习了,虽然好像不实用,但多少是知识
    GreatMartial
        12
    GreatMartial  
       2017-02-05 20:43:16 +08:00
    对小白来说,长知识了,谢谢分享
    XIVN1987
        13
    XIVN1987  
       2017-02-05 20:57:23 +08:00
    讲的很好,不过《 Fluent Python 》这本书里关于这点讲的更全面
    congeec
        14
    congeec  
       2017-02-05 22:03:03 +08:00 via iPhone
    标题就给人下套,又没说 i 是啥类型
    fhefh
        15
    fhefh  
       2017-02-05 22:24:10 +08:00
    直接看最后的结果
    keisuu
        16
    keisuu  
       2017-02-06 00:52:28 +08:00
    貌似只是对可变类型的数据才不等价,顺带关注一下
    chiu
        17
    chiu  
       2017-02-06 07:35:08 +08:00 via Android
    上班路上花点路上的时间看,学习点只是,没什么不好。难道就因为是推广有些人就抵触?
    WildCat
        18
    WildCat  
       2017-02-06 08:59:23 +08:00 via iPhone
    谭浩强笑而不语
    glasslion
        19
    glasslion  
       2017-02-06 12:04:27 +08:00
    @chiu 不是因为推广而抵触, 是因为为了推广吸引眼球就胡说八道
    luobuda
        20
    luobuda  
       2017-02-06 13:24:28 +08:00
    挺好的,只是标题误导性强
    lzjun
        21
    lzjun  
    OP
       2017-02-06 13:54:19 +08:00
    不好意思哈,标题误导了大家,但是内容绝对没有胡说八道,请明察。那些冷嘲热讽的同学不喜欢内容的可以点『忽略主题』
    weyou
        22
    weyou  
       2017-02-06 18:19:07 +08:00
    不管是不是推广,对这个坑的解释还是很到位的。
    siteshen
        23
    siteshen  
       2017-02-07 12:55:11 +08:00
    吓得我赶紧 `help('+=')` 了一下,果然不完全等价。

    An augmented assignment expression like "x += 1" can be rewritten as
    "x = x + 1" to achieve a similar, but not exactly equal effect. In the
    augmented version, "x" is only evaluated once. Also, when possible,
    the actual operation is performed *in-place*, meaning that rather than
    creating a new object and assigning that to the target, the old object
    is modified instead.
    josephshen
        24
    josephshen  
       2017-02-09 01:16:24 +08:00 via iPhone
    唉,大半夜随手看到你写的东西,误人子弟。

    你试试 x = 0 时, a = 1037490 和 a = 1 这两个情况下运算后的 id ?

    你能说明白你写的例子里面 x = 1 时侯, id 差异值的原理吗?

    你文章的问题在于,讲了一大堆,看似讲明白了,但其实核心的原理都没说,看似对人有帮助,但是其实读者没法或者说真的了解现象后的原因,当然这和你自己水平也有关系。

    但是,但是,我要说的这都不是关键,关键是你知道嘛,这里不欢迎全文转载啊
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3068 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 34ms · UTC 14:38 · PVG 22:38 · LAX 06:38 · JFK 09:38
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.