Python 表达式 i += x 与 i = i + x 等价吗? - 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
lzjun
V2EX    Python

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

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

    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
    我觉得题目应该改成 深拷贝浅拷贝 或者值传递地址传递类似的说法,这个标题有标题党的嫌疑
    crb
        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     Solana     5555 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 29ms UTC 07:30 PVG 15:30 LAX 00:30 JFK 03:30
    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