Python 表達式 i += x 與 i = 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 += x 與 i = i + x是不等價的,那什麼情況下等價,什麼情況下不等價呢?
弄清楚這個問題之前,首選得明白兩個概念:可變物件與不可變物件。
在 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。
可變對象(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
+= 作業首先會嘗試呼叫物件的 __iadd__方法,如果沒有此方法,那麼試著呼叫__add__方法,先來看看這兩個方法有何不同
__add__和__iadd__ 區別
__add__ 方法接收兩個參數,傳回它們的和,兩個參數的值都不會改變。
__iadd__ 方法同樣接收兩個參數,但它是屬於in-place 操作,就是說它會改變第一個參數的值,因為這需要物件是可變的,所以對於不可變物件沒有__iadd__方法。
>>> hasattr(int, '__iadd__') False >>> hasattr(list, '__iadd__') True
顯然,整數物件是沒有__iadd__的,而列表物件提供了__iadd__方法。
>>> l2 += [3] # 代码1:使用__iadd__,l2的值原地修改
代碼1中的+= 操作調用的是__iadd__方法,他會原地修改l2指向的那個物件本身的值
>>> l2 = l2 + [3] # 代码2:调用 __add__,创建了一个新的列表,赋值给了l2
而代碼2中的+ 操作調用的是__add__ 方法,此方法會傳回一個新的對象,原來的對象保持不變,l1還是指向原來的對象,而l2已經指向一個新的對象。
以上就是表達式 i += x 與 i = i + x 的差別。因此對於列表進行 += 操作時,會存在潛在的bug,因為l1會因為l2的變化而改變,就像函數的參數不宜使用可變物件作為關鍵字參數一樣。