Python 中 a+=b 和 a=a+b 的區別有哪些?
問題詳細描述在這裡 python問題求助
在看apriori的演算法,決定自己練練手,寫寫代碼。最近又在,所以準備用python實現。其中一個子過程是要求候選項Ck的k項子集。在這個求子集的方法中遇到了問題了,並且很神奇。最開始一直找不到問題所在,當單步調試發現問題所在之處時卻倍感神奇。下面用代碼示例來說明下這個問題的神奇之處。
# coding=UTF-8
"""
Created on Mar 11, 2012
@author: tanglei|www.tanglei.name
"""
elements=[["1"],["2"],["3"]]
def getSubset(k,size):
subset=[]
if k == 1:
subset += (elements[0:size])
return subset
else:
i = size - 1
while i &>= k-1 :
set = getSubset(k-1,i)
j = 0
while j &< len(set): #Attention a+=b a=a+b #set[j] += (elements[i]) #Why Elements change here? set[j] = set[j] + (elements[i]) j += 1 subset += (set) i -= 1 return subset print("elements:",elements) test = getSubset(2,len(elements)) print(test) print("elements",elements)
本例中是求集合[["1"],["2"],["3"]]的2-itemset子集。 看如上代碼中,紅色那句先注釋掉,用a=a+b的形式,運行結果如下。這個結果是正確的,為{1,3}、{2,3}、{1,2}。
若將代碼中換成a+=b的形式,即去掉藍色代碼部分,換成紅色的代碼。意料中,應該結果一樣。但實際結果卻出乎意料。
不但子集沒求對,就連原始的集合elements都被改變了。看了好久搞不定是哪裡出了問題了。之前對a+=b和a=a+b的印象僅僅停留在涉及一個強制類型轉換的原因,難道這個也是嗎?搞不懂,像高人求助……
a+=b
&>&>&> a1 = range(3)
&>&>&> a2 = a1
&>&>&> a2 += [3]
&>&>&> a1
[0, 1, 2, 3]
&>&>&> a2
[0, 1, 2, 3]
a=a+b
&>&>&> a1 = range(3)
&>&>&> a2 = a1
&>&>&> a2 = a2 + [3]
&>&>&> a1
[0, 1, 2]
&>&>&> a2
[0, 1, 2, 3]
顯然,兩者是有區別的,而這種區別只出現在可變對象上(為什麼是可變對象後面再說),是什麼原因造成了兩者的區別呢?
+= 操作調用 __iadd__方法,沒有該方法時,再嘗試調用__add__方法
a1 = [0, 1, 2]
a1 += [3]
# 等價於
a1.__iadd__([3])
print(a1) #[0, 1, 2, 3]
__iadd__方法直接在原對象a1上進行更新,該方法的返回值為None
+ 操作調用__add__方法
a1 = [0, 1, 2]
a1 = a1 + [3]
# 等價於
a1 = a1.__add__([3])
__add__方法會返回一個新的對象,原對象不修改,因為這裡 a1被重新賦值了,a1指向了一個新的對象,所以出現了文章開頭a1不等於a2的情況
a1 = [0, 1, 2]
print(a1.__add__([3])) # [0, 1, 2, 3]
print(a1) # [0, 1, 2]
為什麼前面我說這種差異只會發生的可變對象身上?因為對於不可變對象,根本沒有 __iadd__方法,所以+=和+的效果是一樣的,因為調的都是 __add__ 方法
以上~,不知我寫清楚了沒有,不妨關注一下公眾號:Python之禪
綜合了幾位仁兄的回答和疑問,我想還是用以下代碼來回答吧,希望有幫助。
問題1. int和list是不一樣的,代碼為證:&>&>&> a=1
&>&>&> b=a&>&>&> a+=1&>&>&> a,b(2, 1)&>&>&> a=[1,2,3,4]
&>&>&> b=a&>&>&> a+=[5]&>&>&> a,b([1, 2, 3, 4, 5], [1, 2, 3, 4, 5])
通俗地講,類型為int時,a和b是「不一樣的」;類型為list時,a和b是「一樣的」。術語叫做immutable和mutable,具體原理在這個節點不必深究。
問題1.1. 我們通常運行b=a這一語句時,會直覺地認為,b和a已經不一樣了。代碼為證:&>&>&> a=[[1],[2],[3],[4]]
&>&>&> b+=a[0:2]&>&>&> b[1, 2, 3, 4, [1], [2]]&>&>&> a=[[1],[2],[3],[4]]&>&>&> b=[]
&>&>&> b+=a[0:2]&>&>&> a,b([[1], [2], [3], [4]], [[1], [2]])&>&>&> b[0][1]&>&>&> b[0][0]="changed!"&>&>&> # You don"t expect a to change&>&>&> # However&>&>&> a, b([["changed!"], [2], [3], [4]], [["changed!"], [2]])
可以看到,a[0]的[1]和b[0]的[1]是「一樣的」,因為改變b[0]就會改變a[0](注意不是改變b,是改變b[0]。改變b不會對a有任何影響)
問題2. list的情況下,a+=b和a=a+b是不一樣的,代碼為證:&>&>&> a=[1,2,3,4]
&>&>&> b=a&>&>&> a+=[5]&>&>&> a,b([1, 2, 3, 4, 5], [1, 2, 3, 4, 5])&>&>&> a=[1,2,3,4]&>&>&> b=a&>&>&> a=a+[5]&>&>&> a,b([1, 2, 3, 4, 5], [1, 2, 3, 4])
同樣通俗地講,在+=的情況下,a還是原來的a,和b「一樣」;在+的情況下,a已經不是原來的a了,和b「不一樣」。
問題3. 如果要讓+=和+行為一致,應該怎麼做?代碼為證:&>&>&> import copy
&>&>&> a=[1,2,3,4]&>&>&> b=copy.deepcopy(a)&>&>&> a+=[5]&>&>&> a,b([1, 2, 3, 4, 5], [1, 2, 3, 4])
這與問題2中a=a+b的情況結果一致了。當對list進行b=a時,實際上進行的是「引用」操作;只有使用b=copy.deepcopy(a)才是進行我們通常期望的「拷貝」操作。
問題4. 回到問題中的代碼,當k=1時,以下代碼:subset += (elements[0:size])
根據問題1.1,subset與elements是「一樣的」,因此未來改變subset的元素的操作有可能改變elements的元素。
到這行代碼時,注意set就是遞歸傳遞過來的subset:#set[j] += (elements[i]) #Why Elements change here?
set[j] = set[j] + (elements[i])
根據問題2,+=中set[j]依然是原來的set[j],也就可能是elements的元素。因此
set[j] += elements[i]
可能會等價於
elements[*] += elements[i]
一旦改變了elements的元素,結果自然就不對了。
怎麼解決這個問題?根據問題3,只要保證set與elements是「不一樣的」,就符合程序的邏輯。因此將
subset += (elements[0:size])
改為(記得import copy)
subset += copy.deepcopy(elements[0:size])
就能在+=的情況下正常運行了。
總結:在python中,list類型的賦值b=a進行的引用操作,而非拷貝操作,在需要拷貝操作時,需要加上b=copy.deepcopy(a)。(copy.copy和copy.deepcopy的區別超出問題範疇,有興趣可以google)check this
http://docs.python.org/reference/simple_stmts.html#assignment-statements關於assignment-statements 和 augmented assignment-statements兩段解釋。
可以將=理解為名字綁定,= assignment-statements只是將右邊(文檔裡面的RHS, abbr. for right-hand-side)的表達式的結果(值或對象)綁定到 =左邊的名字上。 而+=之類augmented assignment-statements是對左邊的對象的in-place操作(when possible)。沒有看python實現源碼, 不太清楚具體原理, 猜想應該和這幾個容器類對象實現的__add__和__iadd__即 + 和 += 的實現方式不一樣. int 和 string 作為值不可變對象在這兩個方法的實現應該是一致的.
其實調試一下, 如下圖, 看id的變動就可以得知.令有興趣可參見http://docs.python.org/reference/datamodel.html在Python列表操作符中:
- "+"代表連接操作,其結果是創建了一個新的列表。
- 』+=『是在Python2.0中添加的替換連接操作,顧名思義,等價於extend()方法,實際上是把新列表添加到了舊有列表裡。
感覺上面的答案不是回答這道題啊;
在python中,不同的情況下,這兩個表達式有著很大的區別,如果a,b都是可變對象,例如list,a+=b實際是對a指向的地址上的值進行修改,即運算前後id(a)的值是不變的。而a=a+b是不同的,首先計算等號左邊a+b得到新的值,然後a再指向這個新的值。即運算後id(a)發生了改變; 但是對於不可變對象,例如int,str等,這兩個表達式是一樣的效果。即運算前後id(a)的值一定會發生改變。 道理很簡單,不可變對象指向的地址上的值不能發生改變,只能在新的地址上存運算後的值。
補充一個,
從位元組碼看的話,
&>&>&> def fun():
... a = 1
... a += 2
...
import dis
&>&>&> dis.dis(fun)
2 0 LOAD_CONST 1 (1)
3 STORE_FAST 0 (a)
3 6 LOAD_FAST 0 (a)
9 LOAD_CONST 2 (2)
12 INPLACE_ADD
13 STORE_FAST 0 (a)
16 LOAD_CONST 0 (None)
19 RETURN_VALUE
==========================================================
&>&>&> def fun2():
... a = 1
... a = a+2
...
&>&>&> dis.dis(fun2)
2 0 LOAD_CONST 1 (1)
3 STORE_FAST 0 (a)
3 6 LOAD_FAST 0 (a)
9 LOAD_CONST 2 (2)
12 BINARY_ADD
13 STORE_FAST 0 (a)
16 LOAD_CONST 0 (None)
19 RETURN_VALUE
也可以看出=是binary add, +=是inplace add,也可以理解.
還有一個就是當不可變對象容器(tuple)中使用了可變對象元素的話,如
&>&>&> t = (1,2,[2,3])
&>&>&> t = (1,2,[2,3])
&>&>&> t[2] = t[2] + [3,3]
Traceback (most recent call last):
File "&
TypeError: "tuple" object does not support item assignment
&>&>&> t
(1, 2, [2, 3])
============================ 會發現雖然報錯結果是相同的,但是執行t[2]+= 之後t[2]在賦值操作之前已經被改變. 「增量賦值不是一個原子操作。雖然拋出了異常,但還是完成了操作」 摘錄來自: [巴西] Luciano Ramalho. 「流暢的Python」。 Visualize Python, Java, JavaScript, TypeScript, and Ruby code execution, 這個網站可以對 Python 運行原理進行可視化分析. 可以運行代碼查看右邊的可視化效果. 摘錄來自: [巴西] Luciano Ramalho. 「流暢的Python」。 iBooks.
&>&>&> t[2] += [3,3]
Traceback (most recent call last):
File "&
TypeError: "tuple" object does not support item assignment
&>&>&> t
(1, 2, [2, 3, 3, 3])
&>&>&> def f(a,b): a+=b
...
&>&>&> def g(a,b): a=a+b
...
&>&>&> dis.dis(f)
1 0 LOAD_FAST 0 (a)
3 LOAD_FAST 1 (b)
6 INPLACE_ADD
7 STORE_FAST 0 (a)
10 LOAD_CONST 0 (None)
13 RETURN_VALUE
&>&>&> dis.dis(g)
1 0 LOAD_FAST 0 (a)
3 LOAD_FAST 1 (b)
6 BINARY_ADD
7 STORE_FAST 0 (a)
10 LOAD_CONST 0 (None)
13 RETURN_VALUE
《Python學習手冊》里賦值語法那一節已經講得很清楚了+=對於可變對象來說,以最小動作為基礎,是在原處修改的另外的是生成了一個新的對象
如下題主的問題無關,僅從這兩個表達式的性能上進行參考:
#對於a+=b和a=a+b 的不同點,從性能上可以參考下:
import time
#第一種result=[]start=time.time()for i in range(30000):result += [i]print(len(result),",",time.time()-start)#第二種result=[]start=time.time()for i in range(30000):result = result + [i]print(len(result),",",time.time()-start)得到的結果可以看出,運行3萬次,
第一種運行時間為:0.015625476837158203
第二種運行時間為:2.546924591064453
可見,在某些演算法場景下,需要選擇適當的表達式.
推薦一本《流暢的python》,目前正在學,博大精深啊
因為python的list上a+=b操作,是調用 a. __iadd__(b)實現,而 __iadd__在CPython里實現大約是:
list_inplace_concat(PyListObject *self, PyObject *other)
{
PyObject *result;
result = listextend(self, other);//調用extend,in-place擴展
if (result == NULL)
return result;
Py_DECREF(result);
Py_INCREF(self);
return (PyObject *)self;//返回值是擴展後的列表
}
所以實際上調用extend改變了列表本身。
Why does += behave unexpectedly on lists?
Python 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 07:18:10) [MSC v.1900 32 bit (Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
&>&>&> a1 = range(3)
&>&>&> a2 = a1
&>&>&> a2 += [3]
Traceback (most recent call last):
File "&
", line 1, in &
a2 += [3]
TypeError: unsupported operand type(s) for +=: "range" and "list"
&>&>&>
推薦閱讀:
※現在哪些編程技術比較火爆。需求量很大且待遇看漲?
※你用 Python 做過什麼有趣的數據挖掘/分析項目?
※python程序員路線圖?
※用 Python 可以建網站嗎?
※為什麼在python3里b=a=1是合理表達式,而print(a=1)卻不是。a=1為什麼沒有返回值?
TAG:Python |