理解LEGB前冻河,首先需要對Python的作用域莉擒、命名空間有一定的了解钝计,話題才能繼續(xù)展開恋博。
命名空間
命名空間表示變量的可見范圍,一個變量名可以定義在多個不同的命名空間私恬,相互之間并不沖突债沮,但同一個命名空間中不能有兩個相同的變量名。比如:兩個叫“張三”的學(xué)生可以同時存在于班級A和班級B中本鸣,如果兩個張三都是一個班級疫衩,那么帶來的麻煩復(fù)雜很多了,在Python中你不能這么干荣德。
在Python中用字典來表示一個命名空間闷煤,命名空間中保存了變量(名字)和對象的映射關(guān)系,在Python中命名空間出現(xiàn)在哪些地方呢命爬?有函數(shù)范圍內(nèi)的命名空間(local)曹傀,有模塊范圍內(nèi)的命名空間(global),有python內(nèi)建的命名空間(built-in)饲宛,還有類對象的所有屬性組成的命名空間皆愉。
命名空間的生命周期
所有的命名空間都是有生命周期的,對于python內(nèi)建的命名空間艇抠,python解析器啟動時創(chuàng)建幕庐,一直保留直至直python解析器退出時才消亡。而對于函數(shù)的local命名空間是在函數(shù)每次被調(diào)用的時候創(chuàng)建家淤,調(diào)用完成函數(shù)返回時消亡异剥,而對于模塊的global命名空間是在該模塊被import的時候創(chuàng)建,解析器退出時消亡絮重。
作用域
一個作用域是指一段程序的正文區(qū)域冤寿,可以是一個函數(shù)或一段代碼。一個變量的作用域是指該變量的有效范圍青伤。Python的作用域是靜態(tài)作用域督怜,因為它是由代碼中得位置決定的,而命名空間就是作用域的動態(tài)表現(xiàn)狠角。
LGB
Python2.2之前定義了三個作用域号杠,分別是:
global作用域,對應(yīng)的global命名空間,一個模塊最外層定義的一個作用域姨蟋。
local作用域屉凯,對應(yīng)local命名空間,由函數(shù)定義的眼溶。
builtin作用域悠砚,對應(yīng)builtin命名空間,python內(nèi)部定義的最頂層的作用域偷仿,在這個作用域里面定義了各種內(nèi)建函數(shù):open哩簿、range、xrange酝静、list等等节榜。
那時的Python作用域規(guī)則叫做LEB規(guī)則,變量(名字)的引用按照local作用域别智、global作用域宗苍、builtin作用域的順序來查找。
首先來看一段代碼:
a = 1
def foo():
? ? a = 2
? ? print a? //[1]
print a? ? ? //[2]
foo()
[1]處輸出結(jié)果為2薄榛,Python首先會在函數(shù)foo定義的local作用域中查找名字a讳窟,如果找到了直接輸出,沒有沒找到就會在模塊定義的global作用域中查找敞恋,如果還沒找到丽啡,就到Python內(nèi)建的builtin作用域中查找a,如果還沒找到就報異常:NameError: name 'a' is not defined硬猫。引用過程如圖:
lgb
[2]處輸出結(jié)果為1补箍,查找順序同樣是按照LGB規(guī)則,只不過這里的local作用域就是global作用域啸蜜。
LEGB規(guī)則
Python2.2開始引入嵌套函數(shù)坑雅,嵌套函數(shù)為python提供了閉包實現(xiàn)。
a = 1
def foo():
? a = 2
? def bar():
? ? ? ? print a? //[1]
? ? return bar
func = foo()
func()
函數(shù)bar和a=2捆包在一起組成一個閉包衬横,因此這里a=2即使脫離了foo所在的local作用域裹粤,但調(diào)用func的時候(其實就是調(diào)用bar)查找名字a的順序是LEGB規(guī)則,這里的E就是enclosing的縮寫蜂林,代表的“直接外圍作用域”這個概念遥诉。查找a時,在bar對應(yīng)的local作用域中沒有時噪叙,然后在它外圍的作用域中查找a突那。LEGB規(guī)定了查找一個名稱的順序為:local-->enclosing-->global-->builtin。