命名空間
一個(gè)程序具有多個(gè)變量,為了更好的組織這些變量,更好的控制變量之間的命名沖突以及更好的管理變量在程序中的角色,python使用了命名空間這樣一種結(jié)構(gòu)來(lái)實(shí)現(xiàn)這個(gè)目的。python中,一個(gè)命名空間由多個(gè)變量組成,且以字典的形式來(lái)保存這些變量,其中key是變量名稱,value是變量名對(duì)應(yīng)的對(duì)象。
同一個(gè)命名空間中的變量名是唯一的,不同命名空間中的變量相互獨(dú)立(這里的相互獨(dú)立指的是不會(huì)相互沖突相互覆蓋,但是可能會(huì)有相同的對(duì)象引用(即指向同一個(gè)內(nèi)存中的對(duì)象))。既然命名空間是為了更好的控制變量之間的命名沖突以及更好的管理變量在程序中的角色,那么什么情況下把兩個(gè)變量放入同一個(gè)命名空間,即以什么樣的邏輯和規(guī)則定義變量的命名空間就是最基本的問(wèn)題。
python中約定了四種命名空間:內(nèi)置的(Bulit-in)、全局的(Global)、外層函數(shù)的(Enclosing or Nonlocal)以及本地的(Local)。
這四種命名空間的定義規(guī)則和邏輯如下:
內(nèi)置的(Bulit-in):python定義的所有的內(nèi)置變量,比如abs、max這些內(nèi)置函數(shù)以及預(yù)定義的異常等,這些變量都放在內(nèi)置命名空間中,可以通過(guò)python的標(biāo)準(zhǔn)模塊builtins獲取所有的內(nèi)置變量。
全局的(Global):在模塊(腳本)頂層定義的所有變量,python把這些變量會(huì)放在該模塊的全局命名空間中,可以通過(guò)globals()獲取當(dāng)前位置已經(jīng)定義了的所有全局變量。
本地的(Local):python中函數(shù)的每次調(diào)用會(huì)新創(chuàng)建一個(gè)自己的命名空間,這個(gè)命名空間包含的變量為函數(shù)參數(shù)和函數(shù)中定義的所有變量,這些變量也成為該函數(shù)的本地變量。
外層函數(shù)的(Enclosing or Nonlocal):如果函數(shù)A中嵌套了一個(gè)函數(shù)B,那么對(duì)于內(nèi)層函數(shù)B來(lái)說(shuō),外層函數(shù)A的本地命名空間就是B的外層函數(shù)命名空間。單純從命名空間的角度看,似乎外層函數(shù)命名空間和本地命名空間重復(fù)定義了,因?yàn)槠浔旧砭褪峭鈱雍瘮?shù)的本地命名空間。但從作用域和變量查找的角度看,并不會(huì)重復(fù)定義;實(shí)際上,在python2.2之前,這層命名空間是不存在的,2.2版本的python才加入了這層命名空間,加入后,使得函數(shù)B直接引用函數(shù)A的本地變量變?yōu)榭赡?因此,對(duì)于這種嵌套函數(shù)形式下的外層函數(shù)命名空間,python給了其一個(gè)單獨(dú)的定義。
作用域和LEGB變量查找規(guī)則
定義好命名空間之后,我們知道具有同一個(gè)名稱的不同變量可能同時(shí)存在于多個(gè)不同的命名空間中,那么問(wèn)題來(lái)了:當(dāng)我們引用一個(gè)變量x,x同時(shí)存在于多個(gè)命名空間中,python如何知道我們引用的是哪一個(gè)?python對(duì)此引入了作用域和變量查找規(guī)則來(lái)消除這種歧義性。
域( scope)指的就是程序的文本區(qū)域。python中的域指的就是變量的作用域,某個(gè)變量的作用域指的是這樣一個(gè)區(qū)域在這個(gè)區(qū)域中,python可以直接獲取到該變量,直接獲取的意思是可以直接在該變量的命名空間中找到該變量,而不是通過(guò)屬性訪問(wèn)等其他方式獲取。 所以通過(guò)明確變量的作用域可以讓我們知道某個(gè)變量在程序的哪些地方起作用(可以被直接獲取),同時(shí)約定了變量作用域還可以消除部分的歧義性,因?yàn)閷?duì)于某個(gè)變量并不起作用的區(qū)域,我們自然就不需要在該區(qū)域考慮該變量。
對(duì)于上述四種不同命名空間里的變量,其各自有自己的作用域,分別如下:
全局(變量)作用域:指的就是全局變量的作用域,也就是整個(gè)模塊文件;
內(nèi)置(變量)作用域:指的就是內(nèi)置變量的作用域,包括整個(gè)解釋器環(huán)境;
外層函數(shù)(變量)作用域:也指非本地作用域,指的就是外層函數(shù)中變量的作用域,包括整個(gè)外層函數(shù)下的代碼塊,同時(shí)也是外層函數(shù)的本地作用域;
本地(變量)作用域:就是一個(gè)函數(shù)下定義的本地變量的作用域,為函數(shù)下的代碼塊區(qū)域。
明確上述四種變量的各自作用域后,依然存在歧義性,因?yàn)椴煌臻g中的相同名稱的變量的作用域可能存在重復(fù),比如有一個(gè)全局變量x,在一個(gè)函數(shù)中又定義了一個(gè)本地變量x,那么該函數(shù)中的代碼塊即是全局變量x的作用域,也是本地變量x的作用域。針對(duì)這種情況,python中規(guī)定了變量在不同命名空間中的查找順序,依次為:本地命名空間(L)、外層函數(shù)命名空間(E or N)、全局命名空間(G)以及內(nèi)置命名空間(B),這稱為python變量查找的LEGB(LNGB)規(guī)則。如此,即使不同命名空間中存在相同名稱的變量且作用域重復(fù),根據(jù)該規(guī)則,就可以確定python查找的變量對(duì)應(yīng)的值到底是哪一個(gè),從而可以消除變量名的歧義性。
需要注意的是,LEGB(LNGB)規(guī)則是python將源碼編譯成字節(jié)碼時(shí)的變量作用域解析規(guī)則,所以實(shí)際上,變量的命名空間在編譯時(shí)就被靜態(tài)確定了,從而在解釋器執(zhí)行編譯后的字節(jié)碼時(shí),python會(huì)直接在變量對(duì)應(yīng)的命名空間中去獲取該對(duì)象。
最后強(qiáng)調(diào)一下外層函數(shù)命名空間和作用域,該命名空間是在python2.2版本加入的,在2.2之前的版本,如下代碼如無(wú)法運(yùn)行成功,因?yàn)閷?duì)于inner函數(shù)中的x,python只會(huì)在本地命名空間、全局命名空間和內(nèi)置命名空間中查找,outer函數(shù)的命名空間會(huì)被直接跳過(guò),因此下面的代碼會(huì)因?yàn)檎也坏絰而報(bào)錯(cuò)。2.2之前的版本為了使用outer函數(shù)的x,一般需要把x作為參數(shù)傳給inner。但是在2.2版本開(kāi)始,加入了Enclsoing function概念,也就是outer函數(shù),該函數(shù)的命名空間為外層函數(shù)命名空間,其變量的作用域包含了inner函數(shù)的代碼塊,因此python在查找x變量時(shí),也會(huì)查找該外層函數(shù)的命名空間。