一、賦值、引用
在python中賦值語句總是建立對象的引用值,而不是復制對象。因此,python變量更像是指針,而不是數據存儲區域

這點和大多數語音類似吧,比如C++、Java等
1、先看個例子:
values=[0,1,2] values[1]=values print(values) # [0, [...], 2]
預想應該是:[0,[0,1,2],2],但結果卻為何要賦值無限次?
可以說 Python 沒有賦值,只有引用。你這樣相當於創建了一個引用自身的結構,所以導致了無限循環。為了理解這個問題,有個基本概念需要搞清楚。
Python 沒有「變量」,我們平時所說的變量其實只是「標簽」,是引用。
執行:values=[0,1,2]的時候,python做的事情是首先創建一個列表對象[0,1,2],然后給它貼上名為values的標簽。如果隨后執行values=[3,4,5]
的話,python做的事情是創建另一個列表對象[3,4,5],然后把剛才那張名為values的標簽從前面的[0,1,2]對象上撕下來,重新貼到[3,4,5]這個對象上。
至始至終,並沒有一個叫做 values 的列表對象容器存在,Python 也沒有把任何對象的值復制進 values 去。過程如圖所示:

執行:values[1]=values的時候,python做的事情則是把values這個標簽所引用的列表對象的第二個元素指向values所引用的列表對象本身。執行完畢后,values
標簽還是指向原來那個對象,只不過那個對象的結構發生了變化,從之前的列表[0,1,2]變成了[0,?,2],而這個?則是指向那個對象本身的
一個引用。如圖所示:

要達到你所需要的效果,即得到 [0, [0, 1, 2], 2] 這個對象,你不能直接將 values[1] 指向 values 引用的對象本身,而是需要吧 [0, 1, 2] 這個對象「復制」一遍,得到一個新對象,再將 values[1] 指向這個復制后的對象。Python 里面復制對象的操作因對象類型而異,復制列表 values 的操作是
values[:] # 生成對象的拷貝或者是復制序列,不再是引用和共享變量,但此法只能頂層復制
所以你需要執行:values[1]=values[:]
Python 做的事情是,先 dereference 得到 values 所指向的對象 [0, 1, 2],然后執行 [0, 1, 2][:] 復制操作得到一個新的對象,內容也是 [0, 1, 2],然后將 values 所指向的列表對象的第二個元素指向這個復制二來的列表對象,最終 values 指向的對象是 [0, [0, 1, 2], 2]。過程如圖所示:

往更深處說,values[:] 復制操作是所謂的「淺復制」(shallow copy),當列表對象有嵌套的時候也會產生出乎意料的錯誤,比如
a=[0,[1,2],3] b=a[:] a[0]=8 a[1][1]=9 print(a) # [8, [1, 9], 3] print(b) # [0, [1, 9], 3]
b 的第二個元素也被改變了。想想是為什么?不明白的話看下圖

正確的復制嵌套元素的方法是進行「深復制」(deep copy),方法是
import copy a = [0, [1, 2], 3] b = copy.deepcopy(a) a[0] = 8 a[1][1] = 9

2、引用vs拷貝
(1)沒有限制條件的分片表達式(L[:])能夠復制序列,但此法只能淺層復制。
(2)字典 copy 方法,D.copy() 能夠復制字典,但此法只能淺層復制
(3)有些內置函數,例如 list,能夠生成拷貝 list(L)
(4)copy 標准庫模塊能夠生成完整拷貝:deepcopy 本質上是遞歸 copy
(5)對於不可變對象和可變對象來說,淺復制都是復制的引用,只是因為復制不變對象和復制不變對象的引用是等效的(因為對象不可變,當改變時會新建對象重新賦值)。所以看起來淺復制只復制不可變對象(整數,實數,字符串等),對於可變對象,淺復制其實是創建了一個對於該對象的引用,也就是說只是給同一個對象貼上了另一個標簽而已。
3、增強賦值以及共享引用
x = x + y,x 出現兩次,必須執行兩次,性能不好,合並必須新建對象 x,然后復制兩個列表合並
屬於復制/拷貝
x += y,x 只出現一次,也只會計算一次,性能好,不生成新對象,只在內存塊末尾增加元素。
當 x、y 為list時, += 會自動調用 extend 方法進行合並運算,in-place change。
屬於共享引用
二、深拷貝deepcopy與淺拷貝copy
python中的對象之間賦值時是按引用傳送的,如果需要拷貝對象,需要使用標准庫中的copy模塊
1、copy.copy 淺拷貝,只拷貝父對象,不會拷貝對象的內部的子對象。(子對象(數組)修改,也會修改)
2、copy.deepcopy 深拷貝,拷貝對象及其子對象(原始對象)
import copy a=[1,2,[3,4],{'a':1}] # 原始對象 b=a # 賦值,傳對象的引用 c=copy.copy(a) # 對象拷貝,淺拷貝 d=copy.deepcopy(a) # 對象拷貝,深拷貝 e=a[:] # 能復制序列,淺拷貝 a.append('add1') # 修改對象a a[2].append('add2') # 修改對象a中的[3,4]數組對象 a[3]='666' print('a:',a) print('b:',b) print('c:',c) print('d:',d) print('e:',e)
"""
執行結果:
a: [1, 2, [3, 4, 'add2'], '666', 'add1']
b: [1, 2, [3, 4, 'add2'], '666', 'add1']
c: [1, 2, [3, 4, 'add2'], {'a': 1}]
d: [1, 2, [3, 4], {'a': 1}]
e: [1, 2, [3, 4, 'add2'], {'a': 1}]
解釋:copy.copy 淺拷貝 只拷貝父對象,不會拷貝對象的內部的子對象。子對象(數組)修改,也會修改
copy.deepcopy 深拷貝 拷貝對象及其子對象(原始對象)
"""
三、深入理解python變量作用域及其陷阱
1、可變對象&不可變對象
在Python中,對象分為兩種:可變對象和不可變對象,不可變對象包括int,float,long,str,tuple等,可變對象包括list,set,dict等。需要注意的是:這里說的不可變指的是值的不可變。對於不可變類型的變量,如果要更改變量,則會創建一個新值,把變量綁定到新值上,而舊值如果沒有被引用就等待垃圾回收。另外,不可變的類型可以計算hash值,作為字典的key。可變類型數據對對象操作的時候,不需要再在其他地方申請內存,只需要在此對象后面連續申請(+/-)即可,也就是它的內存地址會保持不變,但區域會變長或者變短。
a='hello' print(id(a)) # 1991608735200 a='python' print(id(a)) # 1991608735368 # 重新賦值之后,變量a的內存地址已經變了 # 'hello'是str類型,不可變,所以賦值操作知識重新創建了str 'python'對象,然后將變量a指向了它 l1=[1,2,3] print(id(l1)) # 2262493958280 l1.append(4) print(id(l1)) # 2262493958280 # list重新賦值之后,變量l1的內存地址並未改變 # [1, 2, 3]是可變的,append操作只是改變了其value,變量l1指向沒有變
2、函數值傳遞
def func_int(a): a+=4 def func_list(l1): l1[0]=4 t=0 func_int(t) print(t) # 0 t_list=[1,2,3] func_list(t_list) print(t_list) # [4, 2, 3]
對於上面的輸出,不少Python初學者都比較疑惑:第一個例子看起來像是傳值,而第二個例子確實傳引用。其實,解釋這個問題也非常容易,主要是因為可變對象和不可變對象的原因:對於可變對象,對象的操作不會重建對象,而對於不可變對象,每一次操作就重建新的對象。
在函數參數傳遞的時候,Python其實就是把參數里傳入的變量對應的對象的引用依次賦值給對應的函數內部變量。參照上面的例子來說明更容易理解,func_int中的局部變量"a"其實是全部變量"t"所指向對象的另一個引用,由於整數對象是不可變的,所以當func_int對變量"a"進行修改的時候,實際上是將局部變量"a"指向到了整數對象"1"。所以很明顯,func_list修改的是一個可變的對象,局部變量"a"和全局變量"t_list"指向的還是同一個對象。
3、陷阱:使用可變的默認參數
我多次見到過如下的代碼: def foo(a, b, c=[]): # append to c # do some more stuff 永遠不要使用可變的默認參數,可以使用如下的代碼代替: def foo(a, b, c=None): if c is None: c = [] # append to c # do some more stuff 與其解釋這個問題是什么,不如展示下使用可變默認參數的影響: In[2]: def foo(a, b, c=[]): ... c.append(a) ... c.append(b) ... print(c) ... In[3]: foo(1, 1) [1, 1] In[4]: foo(1, 1) [1, 1, 1, 1] In[5]: foo(1, 1) [1, 1, 1, 1, 1, 1] 同一個變量c在函數調用的每一次都被反復引用。這可能有一些意想不到的后果。
參考:http://www.cnblogs.com/jiangzhaowei/p/5740913.html
