1. 程式人生 > >Python 表示式 i += x 與 i = i + x 等價嗎?

Python 表示式 i += x 與 i = i + x 等價嗎?

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的變化而發生改變,就像函式的引數不宜使用可變物件作為關鍵字引數一樣。


關注公眾號「Python之禪」(id:vttalk)獲取最新文章 python之禪