【要點(diǎn)搶先看】
上一小節(jié)我們引入了函數(shù)的概念,這一節(jié)我們開(kāi)始接觸函數(shù)里的一個(gè)非常重要的話(huà)題:變量的作用域。 當(dāng)你在一個(gè)程序中使用變量名時(shí),python創(chuàng)建、改變或查找變量名都是在所謂的命名空間中進(jìn)行的,也就是我們要說(shuō)的變量的作用域。在代碼中給一個(gè)變量賦值的地方?jīng)Q定了這個(gè)變量將存在于哪一個(gè)命名空間,也就是他的可見(jiàn)范圍。 def之中的變量名和def之外的變量名并不沖突,一個(gè)在def之外被賦值(例如,在另外一個(gè)def之中或者在模塊文件的頂層)的變量X與在這個(gè)def之中賦值的變量X是完全不同的變量。 所以我們看出,變量的作用域完全是由變量在程序文件中源代碼的位置而決定,而不是由函數(shù)調(diào)用決定。 【妹子說(shuō)】好啦,說(shuō)了這么多概念,還是用例子說(shuō)話(huà)吧! x = 99 這里就可以看出,在這個(gè)模塊文件中:語(yǔ)句X=99,我們創(chuàng)建了一個(gè)名為X的全局變量(在這個(gè)函數(shù)所在的模塊文件中可見(jiàn)),但是X=88這個(gè)賦值語(yǔ)句創(chuàng)建了一個(gè)本地變量X(只在def語(yǔ)句內(nèi)是可見(jiàn)的)。 盡管這兩個(gè)變量都是X,但是他們作用域可以把它們區(qū)別開(kāi)來(lái)。實(shí)際上,函數(shù)的作用域有助于防止程序之中變量名的沖突,并且有助于函數(shù)成為更加獨(dú)立的程序單元。 【妹子說(shuō)】哦,我大概對(duì)作用域有那么點(diǎn)概念了。 那下面我們接著展開(kāi)具體介紹函數(shù)的四個(gè)作用域:LEGB,即L本地作用域,E內(nèi)嵌作用域,G全局作用域和B內(nèi)置作用域。 在一個(gè)函數(shù)中定義的是本地作用域,而模塊(也就是一個(gè)xxx.py文件)中定義的是全局作用域。而內(nèi)置作用域,我們使用時(shí)是直接使用變量名而不需要導(dǎo)入任何模塊,比如一些內(nèi)置的函數(shù)名:print等等 這里再?gòu)?qiáng)調(diào)一下python中所謂的全局作用域: 全局作用域的作用范圍僅限于單個(gè)文件,別被全局二字所迷惑,這里的全局指的是一個(gè)文件的頂層的變量名僅對(duì)于這個(gè)文件內(nèi)部的代碼而言是全局的,在python中聽(tīng)到全局,你就應(yīng)該想到模塊二字。 變量名由模塊文件隔開(kāi),并且必須精確的導(dǎo)入一個(gè)模塊文件才能夠使用這個(gè)文件中使用的變量名。 再說(shuō)說(shuō)本地作用域:每次對(duì)函數(shù)的調(diào)用都創(chuàng)建一個(gè)新的本地作用域,賦值的變量名除非聲明為全局變量或非本地變量,否則均為本地變量。在默認(rèn)的情況下,所有函數(shù)定義的內(nèi)部變量名都位于本地作用域(與函數(shù)調(diào)用相關(guān)的)內(nèi)。 再來(lái)看一個(gè)例子來(lái)演示一下這兩種作用域: x = 99 這個(gè)例子中出現(xiàn)了全局作用域內(nèi)變量名:x和func 因?yàn)閤是在模塊文件頂層注冊(cè)的,所以他是全局變量;他能夠在函數(shù)內(nèi)部進(jìn)行引用,僅僅是引用變量不需要進(jìn)行全局變量聲明。 也有本地作用域變量名,y,z z和參數(shù)y都是本地變量,只在函數(shù)運(yùn)行時(shí)存在,因?yàn)樗麄兌际窃诤瘮?shù)定義內(nèi)部進(jìn)行賦值的,前面我們說(shuō)過(guò)函數(shù)的參數(shù)也是通過(guò)賦值進(jìn)行傳遞的。 這種變量名隔離機(jī)制存在的意義在于本地變量是作為臨時(shí)的變量名,只有在函數(shù)運(yùn)行的時(shí)候才需要它們,例如,y和z都只存在于函數(shù)內(nèi)部,這些變量名不會(huì)與模塊命名空間內(nèi)的變量名(同理,與其他函數(shù)內(nèi)的變量名)產(chǎn)生沖突。 【妹子說(shuō)】那么如果一段程序中,不同作用域內(nèi)的幾個(gè)變量名稱(chēng)相同怎么辦? 好問(wèn)題!這正是python的LEGB變量名搜索機(jī)制要解決的問(wèn)題。 當(dāng)在python中使用某個(gè)變量名時(shí),python按照L-E-G-B的順序依次搜索四個(gè)作用域,L本地作用域,E即上一層def或者lambda的本地作用域,之后是全局作用域G,最后是內(nèi)置作用域B,并且在第一處能找到作用名的地方停下來(lái),如果變量名在這一次搜索中沒(méi)有找到,python會(huì)報(bào)錯(cuò)。 因此按照LEGB法則中規(guī)定的變量搜索順序,在本地作用域中的變量名是會(huì)在本地作用域中覆蓋在全局作用域和內(nèi)置作用域中有相同變量名的變量,全局變量名會(huì)覆蓋內(nèi)置的同名變量名。 【妹子說(shuō)】話(huà)不多說(shuō),上例子 x = 88 在這一段程序中,本地變量名x覆蓋了全局變量名x,此時(shí)本地和全局的兩個(gè)變量雖然都叫x,但他們是完全不同的變量。 def func(): 這個(gè)例子中,本地作用域中的變量名open就覆蓋了內(nèi)置作用域中的變量名open,因此再使用open去打開(kāi)文件,此時(shí)的操作就無(wú)法使用,因?yàn)槲募蜷_(kāi)的open函數(shù)變量被open=1這個(gè)本地?cái)?shù)值變量覆蓋了。 強(qiáng)調(diào)一點(diǎn):這里我們提到的只是在本地作用域去引用或者覆蓋全局變量和內(nèi)置變量。 但是,請(qǐng)注意!如果試圖去修改,即在函數(shù)內(nèi)部試圖改變函數(shù)外部聲明的值,那就得用global和nonlocal關(guān)鍵字了。 global關(guān)鍵字 之前我們說(shuō)過(guò)python中的變量不用聲明,直接賦值使用,但是這個(gè)global關(guān)鍵字看上去就像一個(gè)聲明,但是他不是一個(gè)類(lèi)型的聲明,而是一個(gè)變量命名空間的聲明,它告訴python函數(shù)打算生成一個(gè)或多個(gè)全局變量。應(yīng)用他,就可以在函數(shù)內(nèi)部對(duì)全局變量進(jìn)行引用和修改 x = 88 在這個(gè)例子中,我們對(duì)X加了一個(gè)global聲明,以便在def之內(nèi)引用并修改位于全局的變量x,而不是產(chǎn)生一個(gè)新的本地變量x并將其覆蓋 我們?cè)倏匆粋€(gè)綜合的例子,串聯(lián)起剛剛我們提到的幾個(gè)知識(shí)點(diǎn) x,y,z = 1,2,3 這個(gè)例子中,x,y,z都是全局變量,y和z只是引用值,而對(duì)于x,我們想改變他的值,因此用了global進(jìn)行引用聲明。 【妹子說(shuō)】恩,作用域的內(nèi)容也是不少呀,LEGB,本地作用域、全局作用域、內(nèi)置作用域,包括他們的引用索引順序,但是好像掉了一條吧。 對(duì),還有嵌套作用域,這個(gè)是一個(gè)更加pythonic的內(nèi)容,我們下一節(jié)單獨(dú)花一節(jié)的篇幅進(jìn)行介紹。 |
|