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

JanLorenzin 8年前
   <p>题图:unsplash.com by Dmitry Pavlov</p>    <p>Python 表达式 <strong> i += x </strong> 与 <strong> i = i + x </strong> 等价吗?如果你的回答是yes,那么恭喜你正确了50%,为什么说只对了一半呢? 按照我们的一般理解它们俩是等价的,整数操作时两者没什么异同,但是对于列表操作,是不是也一样呢?先看下面两段代码:</p>    <p>代码1</p>    <pre>  <code class="language-python">>>> l1 = range(3)  >>> l2 = l1  >>> l2 += [3]  >>> l1  [0, 1, 2, 3]  >>> l2  [0, 1, 2, 3]</code></pre>    <p>代码2</p>    <pre>  <code class="language-python">>>> l1 = range(3)  >>> l2 = l1  >>> l2 = l2 + [3]  >>> l1  [0, 1, 2]  >>> l2  [0, 1, 2, 3]</code></pre>    <p>代码1与代码2中的 l2 的值是一样的,但是 l1 的值却不一样,说明 i += x 与 i = i + x 是不等价的,那什么情况下等价,什么情况下不等价呢?</p>    <p>弄清楚这个问题之前,首选得明白两个概念:可变对象与不可变对象。</p>    <p>在 Python 中任何对象都有的三个通用属性:唯一标识、类型、值。</p>    <p>唯一标识:用于标识对象的在内存中唯一性,它在对象创建之后就不会再改变,函数 id() 可以查看对象的唯一标识</p>    <p>类型:决定了该对象支持哪些操作,不同类型的对象支持的操作就不一样,比如列表可以有length属性,而整数没有。同样地对象的类型一旦确定了就不会再变,函数 type() 可以返回对象的类型信息。</p>    <p>对象的 <strong>值</strong> 与唯一标识不一样,并不是所有的对象的值都是一成不变的,有些对象的值可以通过某些操作发生改变,值可以变化的对象称之为可变对象(mutable),值不能改变的对象称之为不可变对象(immutable)</p>    <h3>不可变对象(immutable)</h3>    <p>对于不可变对象,值永远是刚开始创建时候的值,对该对象做的任何操作都会导致一个新的对象的创建。</p>    <pre>  <code class="language-python">>>> a = 1  >>> id(a)  32574568  >>> a += 1  >>> id(a)  32574544</code></pre>    <p>整数 “1” 是一个不可变对象,最初赋值的时候, a 指向的是整数对象 1 ,但对变量a执行 += 操作后, a 指向另外一个整数对象 2 ,但对象 1 还是在那里没有发生任何变化,而 变量 a 已经指向了一个新的对象2。常见的不可变对象有:int、tuple、set、str。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/f4910fe436aa442c92a50f1a445d10b5.png"></p>    <h3>可变对象(mutable)</h3>    <p>可变对象的值可以通过某些操作动态的改变,比如列表对象,可以通过append方法不断地往列表中添加元素,该列表的值就在不断的处于变化中,一个可变对象赋值给两个变量时,他们共享同一个实例对象,指向相同的内存地址,对其中任何一个变量操作时,同时也会影响另外一个变量。</p>    <pre>  <code class="language-python">>>> 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</code></pre>    <p style="text-align:center"><img src="https://simg.open-open.com/show/d686087926addb083beb38820eb7413d.png"></p>    <p>执行append操作后,对象的内存地址不会改变,x、y 依然指向的是原来同一个对象,只不过是他的值发生了变化而已。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/70885d87425fc0fa91205aa0aa5c0535.png"></p>    <p>理解完可变对象与不可变对象后,回到问题本身, += 与 + 的区别在哪里呢?</p>    <p>+= 操作首先会尝试调用对象的  __iadd__ 方法,如果没有该方法,那么尝试调用 __add__ 方法 ,先来看看这两个方法有什么区别</p>    <h3>__add__和 <em>__</em> iadd <em> <em>__</em> </em> 的区别</h3>    <ul>     <li> <p>__add__ 方法接收两个参数,返回它们的和,两个参数的值均不会改变。</p> </li>     <li> <p>__iadd__ 方法同样接收两个参数,但它是属于 in-place 操作,就是说它会改变第一个参数的值,因为这需要对象是可变的,所以对于不可变对象没有__iadd__方法。</p> </li>    </ul>    <pre>  <code class="language-python">>>> hasattr(int, '__iadd__')  False  >>> hasattr(list, '__iadd__')  True</code></pre>    <p>显然,整数对象是没有__iadd__的,而列表对象提供了__iadd__方法。</p>    <pre>  <code class="language-python">>>> l2 += [3]  # 代码1:使用__iadd__,l2的值原地修改</code></pre>    <p>代码1中的 += 操作调用的是__iadd <em>__</em> 方法,他会原地修改l2指向的那个对象本身的值</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/8bf1eb3c69295bb7fc894070fa90af11.png"></p>    <pre>  <code class="language-python">>>> l2 = l2 + [3]  # 代码2:调用 __add__,创建了一个新的列表,赋值给了l2</code></pre>    <p>而代码2中的 + 操作调用的是 __add <em>__</em> 方法,该方法会返回一个新的对象,原来的对象保持不变,l1还是指向原来的对象,而l2已经指向一个新的对象。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c0e7c631b893beaec0d19c88db53c93b.png"></p>    <p>以上就是表达式 i += x 与 i = i + x 的区别。因此对于列表进行 += 操作时,会存在潜在的bug,因为l1会因为l2的变化而发生改变,就像函数的参数不宜使用可变对象作为关键字参数一样。</p>    <p> </p>    <p>来自:http://mp.weixin.qq.com/s/jluii9YIvfhKd_tPecfTaw</p>    <p> </p>