解析Python中的變量、引用、拷貝和作用域的問(wèn)題
在Python中,變量是沒(méi)有類型的,這和以往看到的大部分編輯語(yǔ)言都不一樣。在使用變量的時(shí)候,不需要提前聲明,只需要給這個(gè)變量賦值即可。但是,當(dāng)用變量的時(shí)候,必須要給這個(gè)變量賦值;如果只寫(xiě)一個(gè)變量,而沒(méi)有賦值,那么Python認(rèn)為這個(gè)變量沒(méi)有定義。如下:
>>> a Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'a' is not defined
下面我們具體講一下Python中的變量,引用,拷貝和作用域問(wèn)題。。
一、可變對(duì)象 & 不可變對(duì)象
在Python中,對(duì)象分為兩種:可變對(duì)象和不可變對(duì)象,不可變對(duì)象包括int,float,long,str,tuple等,可變對(duì)象包括list,set,dict等。需要注意的是:這里說(shuō)的不可變指的是值的不可變。對(duì)于不可變類型的變量,如果要更改變量,則會(huì)創(chuàng)建一個(gè)新值,把變量綁定到新值上,而舊值如果沒(méi)有被引用就等待垃圾回收。另外,不可變的類型可以計(jì)算hash值,作為字典的key??勺冾愋蛿?shù)據(jù)對(duì)對(duì)象操作的時(shí)候,不需要再在其他地方申請(qǐng)內(nèi)存,只需要在此對(duì)象后面連續(xù)申請(qǐng)(+/-)即可,也就是它的內(nèi)存地址會(huì)保持不變,但區(qū)域會(huì)變長(zhǎng)或者變短。
下面是一些例子:
>>> a = 'xianglong.me' >>> id(a) 140443303134352 >>> a = '1saying.com' >>> id(a) 140443303131776 # 重新賦值之后,變量a的內(nèi)存地址已經(jīng)變了 # 'xianglong.me'是str類型,不可變,所以賦值操作知識(shí)重新創(chuàng)建了str '1saying.com'對(duì)象,然后將變量a指向了它
>>> a_list = [1, 2, 3] >>> id(a_list) 140443302951680 >>> a_list.append(4) >>> id(a_list) 140443302951680 # list重新賦值之后,變量a_list的內(nèi)存地址并未改變 # [1, 2, 3]是可變的,append操作只是改變了其value,變量a_list指向沒(méi)有變
二、變量無(wú)類型,對(duì)象有類型

三、函數(shù)值傳遞
先看一個(gè)例子:
def func_int(a): a += 4 def func_list(a_list): a_list[0] = 4 t = 0 func_int(t) print t # output: 0 t_list = [1, 2, 3] func_list(t_list) print t_list # output: [4, 2, 3]
對(duì)于上面的輸出,不少Python初學(xué)者都比較疑惑:第一個(gè)例子看起來(lái)像是傳值,而第二個(gè)例子確實(shí)傳引用。其實(shí),解釋這個(gè)問(wèn)題也非常容易,主要是因?yàn)榭勺儗?duì)象和不可變對(duì)象的原因:對(duì)于可變對(duì)象,對(duì)象的操作不會(huì)重建對(duì)象,而對(duì)于不可變對(duì)象,每一次操作就重建新的對(duì)象。
在函數(shù)參數(shù)傳遞的時(shí)候,Python其實(shí)就是把參數(shù)里傳入的變量對(duì)應(yīng)的對(duì)象的引用依次賦值給對(duì)應(yīng)的函數(shù)內(nèi)部變量。參照上面的例子來(lái)說(shuō)明更容易理解,func_int中的局部變量"a"其實(shí)是全部變量"t"所指向?qū)ο蟮牧硪粋€(gè)引用,由于整數(shù)對(duì)象是不可變的,所以當(dāng)func_int對(duì)變量"a"進(jìn)行修改的時(shí)候,實(shí)際上是將局部變量"a"指向到了整數(shù)對(duì)象"1"。所以很明顯,func_list修改的是一個(gè)可變的對(duì)象,局部變量"a"和全局變量"t_list"指向的還是同一個(gè)對(duì)象。
四、淺拷貝 & 深拷貝
接下來(lái)的問(wèn)題是:如果我們一定要復(fù)制一個(gè)可變對(duì)象的副本怎么辦?簡(jiǎn)單的賦值已經(jīng)證明是不可行的,所以Python提供了copy模塊,專門(mén)用于復(fù)制可變對(duì)象。copy中有兩個(gè)方法:copy()和deepcopy(),前一個(gè)是淺拷貝,后一個(gè)是深拷貝。淺拷貝僅僅復(fù)制了第一個(gè)傳給它的對(duì)象,下面的不管了;而深拷貝則將所有能復(fù)制的對(duì)象都復(fù)制了。下面是一個(gè)例子:
a = [[1, 2, 3], [4, 5, 6]] b = a c = copy.copy(a) d = copy.deepcopy(a) a.append(15) a[1][2] = 10 print a print b print c print d # [[1, 2, 3], [4, 5, 10], 15] # [[1, 2, 3], [4, 5, 10], 15] # [[1, 2, 3], [4, 5, 10]] # [[1, 2, 3], [4, 5, 6]]
五、作用域
在Python程序中創(chuàng)建、改變或查找變量名時(shí),都是在一個(gè)保存變量名的地方進(jìn)行中,那個(gè)地方我們稱之為命名空間。作用域這個(gè)術(shù)語(yǔ)也稱之為命名空間。具體地說(shuō),在代碼中變量名被賦值(Python中變量聲明即賦值,global 聲明的只是變量的使用域)的位置決定了該變量能被訪問(wèn)的范圍。函數(shù)定義了本地作用域,而模塊定義的是全局作用域。
每一個(gè)模塊都是全局作用域。也就是說(shuō),創(chuàng)建于模塊文件頂層的變量具有全局作用域,對(duì)于外部訪問(wèn)就成了一個(gè)模塊對(duì)象的屬性。全局作用域的作用范圍僅限于單個(gè)文件。“全局”指的是在一個(gè)文件的頂層變量名對(duì)于這個(gè)文件而言是全局的。每次對(duì)函數(shù)的調(diào)用都創(chuàng)建了一個(gè)新的本地作用域。Python中也有遞歸,即可以調(diào)用自身,每次調(diào)用都會(huì)創(chuàng)建五個(gè)新的本地命名空間。賦值的變量名除非聲明為全局變量,否則均為本地變量。如果需要在函數(shù)內(nèi)部對(duì)模塊文件頂層的變量名賦值,需要在函數(shù)內(nèi)部通過(guò) global 語(yǔ)句聲明該變量。所有的變量可歸納為本地、全局或者內(nèi)置三種。范圍分別為def內(nèi)部,在一個(gè)模塊的命名空間內(nèi)部和預(yù)定義的 __builtin__ 模塊提供的變量。
變量名引用分為三個(gè)作用域進(jìn)行查找:首先是本地,然后是函數(shù)內(nèi)(如果有的話),之后是全局,最后是內(nèi)置。在默認(rèn)情況下,變量名賦值會(huì)創(chuàng)建或者改變本地變量。全局聲明將會(huì)給映射到模塊文件內(nèi)部的作用域的變量名賦值。Python 的變量名解析機(jī)制也稱為 LEGB 法則,具體如下:
當(dāng)在函數(shù)中使用未確定的變量名時(shí),Python搜索4個(gè)作用域:本地作用域(L),之后是上一層嵌套結(jié)構(gòu)中 def 或 lambda 的本地作用域(E),之后是全局作用域(G),最后是內(nèi)置作用域(B)。按這個(gè)查找原則,在第一處找到的地方停止。如果沒(méi)有找到,Python 會(huì)報(bào)錯(cuò)的。下圖說(shuō)明了搜索流程(由內(nèi)及外):
上面說(shuō)了,Python中的變量是沒(méi)有類型的,但Python其實(shí)是區(qū)分類型的:Python的所有變量其實(shí)都是指向內(nèi)存中的對(duì)象的一個(gè)指針,都是值的引用,而其類型是跟著對(duì)象走的。總結(jié)來(lái)說(shuō):在Python中,類型是屬于對(duì)象的,而不是變量, 變量和對(duì)象是分離的,對(duì)象是內(nèi)存中儲(chǔ)存數(shù)據(jù)的實(shí)體,變量則是指向?qū)ο蟮闹羔?。在《Learning Python》一書(shū)中有一個(gè)觀點(diǎn):變量無(wú)類型,對(duì)象有類型,大概也是說(shuō)的這個(gè)意思。下面是一張說(shuō)明變量的圖:

Python像PHP一樣提供了一個(gè)global語(yǔ)法,global定義的本地變量會(huì)變成其對(duì)應(yīng)全局變量的一個(gè)別名,即是同一個(gè)變量。下面的例子可以幫你更好的理解:
a = 44 def test1(): a = 14 print a test1() # 輸出:14 def test2(): global a print a test2() # 輸出:44
相關(guān)文章
PyTorch實(shí)現(xiàn)線性回歸詳細(xì)過(guò)程
本文介紹PyTorch實(shí)現(xiàn)線性回歸,線性關(guān)系是一種非常簡(jiǎn)單的變量之間的關(guān)系,因變量和自變量在線性關(guān)系的情況下,可以使用線性回歸算法對(duì)一個(gè)或多個(gè)因變量和自變量間的線性關(guān)系進(jìn)行建模,該模型的系數(shù)可以用最小二乘法進(jìn)行求解,需要的朋友可以參考一下2022-03-03
Python3.6+selenium2.53.6自動(dòng)化測(cè)試_讀取excel文件的方法
這篇文章主要介紹了Python3.6+selenium2.53.6自動(dòng)化測(cè)試_讀取excel文件的方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-09-09
Python3實(shí)現(xiàn)捕獲Ctrl+C終止信號(hào)
這篇文章主要為大家詳細(xì)介紹了如何利用Python3實(shí)現(xiàn)捕獲Ctrl+C終止信號(hào)的功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-03-03
Python工程實(shí)踐之np.loadtxt()讀取數(shù)據(jù)
為了方便使用和記憶,有時(shí)候我們會(huì)把numpy.loadtxt()縮寫(xiě)成np.loadtxt(),這篇文章主要給大家介紹了關(guān)于Python工程實(shí)踐之np.loadtxt()讀取數(shù)據(jù)的相關(guān)資料,需要的朋友可以參考下2023-07-07
Python函數(shù)默認(rèn)返回None的原因及分析
Python函數(shù)默認(rèn)返回None是因?yàn)樵谡Z(yǔ)法層面,解釋器會(huì)主動(dòng)地為沒(méi)有return語(yǔ)句的函數(shù)添加一個(gè)返回邏輯,返回值為None2024-11-11
python3爬蟲(chóng)中引用Queue的實(shí)例講解
在本篇內(nèi)容里小編給大家整理的是一篇關(guān)于python3爬蟲(chóng)中引用Queue的實(shí)例講解內(nèi)容,有興趣的朋友們可以學(xué)習(xí)下。2020-11-11

