日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

深入理解 python 中的赋值、引用、拷贝、作用域

發布時間:2024/8/26 python 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入理解 python 中的赋值、引用、拷贝、作用域 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

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


這點和大多數 OO 語言類似吧,比如 C++、java 等 ~

1、先來看個問題吧:

在Python中,令values=[0,1,2];values[1]=values,為何結果是[0,[...],2]?

>>>?values?=?[0,?1,?2] >>>?values[1]?=?values >>>?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

問:此時 a 和 b 分別是多少??

正確答案是 a 為 [8, [1, 9], 3],b 為 [0, [1, 9], 3]。發現沒?b 的第二個元素也被改變了。想想是為什么?不明白的話看下圖?

正確的復制嵌套元素的方法是進行「深復制」(deep copy),方法是

import?copya?=?[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)對于不可變對象和可變對象來說,淺復制都是復制的引用,只是因為復制不變對象和復制不變對象的引用是等效的(因為對象不可變,當改變時會新建對象重新賦值)。所以看起來淺復制只復制不可變對象(整數,實數,字符串等),對于可變對象,淺復制其實是創建了一個對于該對象的引用,也就是說只是給同一個對象貼上了另一個標簽而已。

L?=?[1,?2,?3] D?=?{'a':1,?'b':2} A?=?L[:] B?=?D.copy() print?"L,?D" print??L,?D print?"A,?B" print?A,?B print?"--------------------" A[1]?=?'NI' B['c']?=?'spam' print?"L,?D" print??L,?D print?"A,?B" print?A,?BL,?D [1,?2,?3]?{'a':?1,?'b':?2} A,?B [1,?2,?3]?{'a':?1,?'b':?2} -------------------- L,?D [1,?2,?3]?{'a':?1,?'b':?2} A,?B [1,?'NI',?3]?{'a':?1,?'c':?'spam',?'b':?2}

3、增強賦值以及共享引用:

x = x + y,x 出現兩次,必須執行兩次,性能不好,合并必須新建對象 x,然后復制兩個列表合并

屬于復制/拷貝

x += y,x 只出現一次,也只會計算一次,性能好,不生成新對象,只在內存塊末尾增加元素。

當 x、y 為list時, += 會自動調用 extend 方法進行合并運算,in-place change。

屬于共享引用

L?=?[1,?2] M?=?L L?=?L?+?[3,?4] print?L,?M print?"-------------------" L?=?[1,?2] M?=?L L?+=?[3,?4] print?L,?M[1,?2,?3,?4]?[1,?2] ------------------- [1,?2,?3,?4]?[1,?2,?3,?4]

4、python 從 2k 到 3k,語句變函數引發的變量作用域問題 ?

先看段代碼:

def?test():a?=?Falseexec?("a?=?True")print?("a?=?",?a) test()b?=?False exec?("b?=?True") print?("b?=?",?b)

在 python 2k 和 3k 下 你會發現他們的結果不一樣:

2K: a?=??True b?=??True3K: a?=??False b?=??True

這是為什么呢?

因為 3k 中 exec 由語句變成函數了,而在函數中變量默認都是局部的,也就是說

你所見到的兩個 a,是兩個不同的變量,分別處于不同的命名空間中,而不會沖突。

具體參考 《learning python》P331-P332

知道原因了,我們可以這么改改:

def?test():a?=?Falseldict?=?locals()exec("a=True",globals(),ldict)a?=?ldict['a']print(a)test()b?=?False exec("b?=?True",?globals()) print("b?=?",?b)

這個問題在??stackoverflow 上已經有人問了,而且 python 官方也有人報了 bug。。。

具體鏈接在下面:

http://stackoverflow.com/questions/7668724/variables-declared-in-execed-code-dont-become-local-in-python-3-documentatio

http://bugs.python.org/issue4831

http://stackoverflow.com/questions/1463306/how-does-exec-work-with-locals

這是一個典型的 python 2k 移植到 3k 不兼容的案例,類似的還有很多,也算是移植的坑吧~

具體的 2k 與 3k 有哪些差異可以看這里:

使用 2to3 將代碼移植到 Python 3

http://woodpecker.org.cn/diveintopython3/porting-code-to-python-3-with-2to3.html

5、深入理解 python 變量作用域及其陷阱

5.1?可變對象 & 不可變對象

在Python中,對象分為兩種:可變對象和不可變對象,不可變對象包括int,float,long,str,tuple等,可變對象包括list,set,dict等。需要注意的是:這里說的不可變指的是值的不可變。對于不可變類型的變量,如果要更改變量,則會創建一個新值,把變量綁定到新值上,而舊值如果沒有被引用就等待垃圾回收。另外,不可變的類型可以計算hash值,作為字典的key。可變類型數據對對象操作的時候,不需要再在其他地方申請內存,只需要在此對象后面連續申請(+/-)即可,也就是它的內存地址會保持不變,但區域會變長或者變短。

>>>?a?=?'xianglong.me' >>>?id(a) 140443303134352 >>>?a?=?'1saying.com' >>>?id(a) 140443303131776 #?重新賦值之后,變量a的內存地址已經變了 #?'xianglong.me'是str類型,不可變,所以賦值操作知識重新創建了str?'1saying.com'對象,然后將變量a指向了它>>>?a_list?=?[1,?2,?3] >>>?id(a_list) 140443302951680 >>>?a_list.append(4) >>>?id(a_list) 140443302951680 #?list重新賦值之后,變量a_list的內存地址并未改變 #?[1,?2,?3]是可變的,append操作只是改變了其value,變量a_list指向沒有變

5.2?函數值傳遞

def?func_int(a):a?+=?4def?func_list(a_list):a_list[0]?=?4t?=?0 func_int(t) print?t #?output:?0t_list?=?[1,?2,?3] func_list(t_list) print?t_list #?output:?[4,?2,?3]

?對于上面的輸出,不少Python初學者都比較疑惑:第一個例子看起來像是傳值,而第二個例子確實傳引用。其實,解釋這個問題也非常容易,主要是因為可變對象和不可變對象的原因:對于可變對象,對象的操作不會重建對象,而對于不可變對象,每一次操作就重建新的對象。

? ? 在函數參數傳遞的時候,Python其實就是把參數里傳入的變量對應的對象的引用依次賦值給對應的函數內部變量。參照上面的例子來說明更容易理解,func_int中的局部變量"a"其實是全部變量"t"所指向對象的另一個引用,由于整數對象是不可變的,所以當func_int對變量"a"進行修改的時候,實際上是將局部變量"a"指向到了整數對象"1"。所以很明顯,func_list修改的是一個可變的對象,局部變量"a"和全局變量"t_list"指向的還是同一個對象。

5.3?為什么修改全局的dict變量不用global關鍵字

為什么修改字典d的值不用global關鍵字先聲明呢?

s?=?'foo' d?=?{'a':1} def?f():s?=?'bar'd['b']?=?2 f() print?s??#?foo print?d??#?{'a':?1,?'b':?2}

這是因為,在s = 'bar'這句中,它是“有歧義的“,因為它既可以是表示引用全局變量s,也可以是創建一個新的局部變量,所以在python中,默認它的行為是創建局部變量,除非顯式聲明global,global定義的本地變量會變成其對應全局變量的一個別名,即是同一個變量。

在d['b']=2這句中,它是“明確的”,因為如果把d當作是局部變量的話,它會報KeyError,所以它只能是引用全局的d,故不需要多此一舉顯式聲明global。

上面這兩句賦值語句其實是不同的行為,一個是rebinding(不可變對象), 一個是mutation(可變對象).

但是如果是下面這樣:

d?=?{'a':1} def?f():d?=?{}d['b']?=?2 f() print?d??#?{'a':?1}

在d = {}這句,它是”有歧義的“了,所以它是創建了局部變量d,而不是引用全局變量d,所以d['b']=2也是操作的局部變量。

推而遠之,這一切現象的本質就是”它是否是明確的“。

仔細想想,就會發現不止dict不需要global,所有”明確的“東西都不需要global。因為int類型str類型之類的不可變對象,每一次操作就重建新的對象,他們只有一種修改方法,即x = y, 恰好這種修改方法同時也是創建變量的方法,所以產生了歧義,不知道是要修改還是創建。而dict/list/對象等可變對象,操作不會重建對象,可以通過dict['x']=y或list.append()之類的來修改,跟創建變量不沖突,不產生歧義,所以都不用顯式global。

5.4 可變對象 list 的 = 和 append/extend 差別在哪?

接上面 5.3 的理論,下面咱們再看一例常見的錯誤:

#?coding=utf-8 #?測試utf-8編碼 import?sys reload(sys) sys.setdefaultencoding('utf-8')list_a?=?[] def?a():list_a?=?[1]??????##?語句1 a() print?list_a????#?[]print?"======================"list_b?=?[] def?b():list_b.append(1)????##?語句2 b() print?list_b????#?[1]

大家可以看到為什么 語句1 不能改變 list_a 的值,而 語句2 卻可以?他們的差別在哪呢?

因為 = 創建了局部變量,而 .append() 或者 .extend() 重用了全局變量。

5.5 陷阱:使用可變的默認參數

我多次見到過如下的代碼:

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在函數調用的每一次都被反復引用。這可能有一些意想不到的后果。

總結

以上是生活随笔為你收集整理的深入理解 python 中的赋值、引用、拷贝、作用域的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。