Python 名稱空間

Dus發表於2016-05-31

通俗的來說,Python中所謂的名稱空間可以理解為一個容器。在這個容器中可以裝許多識別符號。不同容器中的同名的識別符號是不會相互衝突的。理解python的名稱空間需要掌握三條規則:

第一,賦值(包括顯式賦值和隱式賦值)產生識別符號,賦值的地點決定識別符號所處的名稱空間。

第二,函式定義(包括def和lambda)產生新的名稱空間。

第三,python搜尋一個識別符號的順序是"LEGB"。

所謂的"LEGB"是python中四層名稱空間的英文名字首字母的縮寫。
最裡面的一層是L(local),表示在一個函式定義中,而且在這個函式裡面沒有再包含函式的定義。
第二層E(enclosing function),表示在一個函式定義中,但這個函式裡面還包含有函式的定義,其實L層和E層只是相對的。
第三層G(global),是指一個模組的名稱空間,也就是說在一個.py檔案中定義的識別符號,但不在一個函式中。
第四層B(builtin),是指python直譯器啟動時就已經具有的名稱空間,之所以叫builtin是因為在python直譯器啟動時會自動載入__builtin__模組,這個模組中的list、str等內建函式的就處於B層的名稱空間中。

這三條規則通過一個例子來看比較明白。如下面例子所示:

1 >>> g = int('0x3', 0)
2 >>> def outFunc():
3   e = 2
4     g = 10
5   def inFunc():
6      l = 1
7      return g + e
8     return inFunc()
9 >>> outFunc() ===> 12

來詳細看看這段程式碼中的識別符號。
第1行,適用第一條規則“賦值產生識別符號”,因此產生一個識別符號g。“賦值的地點決定識別符號所處的名稱空間”,因為g是沒有在一個函式定義中,因此g處於'G'層名稱空間中。這一行中還有一個識別符號,那就是int。那麼int是在什麼地方定義的呢?由於int是內建函式,是在__builtin__模組中定義的,所以int就處於'B'的層名稱空間中。
第2行,適用第一條規則,由於def中包含一個隱性的賦值過程,這一行產生一個識別符號outFunc,outFunc並不處於一個函式定義的內部,因此,outFunc處於'G'層名稱空間中。此外,這一行還適用第二條規則,產生一個新的名稱空間。
第3行,適用第一條規則,產生個識別符號e,而且由於這是在一個函式定義內,並且內部還有函式定義,因此e處於'E'層名稱空間中。
第4行要注意,適用第一條規則,產生一個識別符號g,這個g與e一樣外於'E'層名稱空間中。這個g與第一行的g是不同的,因為所處的名稱空間不一樣。
第5行,適用第一條規則,產生一個處於'E'層名稱空間的識別符號inFunc。與第2行一樣,這一行定義函式也產生一個新的名稱空間。
第6行,適用第一條規則,產生一個識別符號l,由於這個l處於一個函式內部,而且在這個函式內部沒有其他函式的定義,因此l處於'L'層名稱空間中。
第7行,適用第三條規則,python直譯器首先看到識別符號g,按照LEGB的順序往上找,先找L層(也就是在inFunc內部),沒有。再找E層,有,值為10。因此這裡的g的值為10。尋找過程到為止,並不會再往上找到'G'層。尋找e的過程也一樣,e的值為2。因此第9行的結果為12。

其實,所謂的“LEGB”是為了學術上便於表述而創造的。讓一個程式設計的人說出哪個識別符號處於哪個層沒有什麼意義,只要知道對於一個識別符號,python是怎麼尋找它的值的就可以了。其實找值的過程直觀上也很容易理解。

通過上面的例子也可以看出,如果在不同的名稱空間中定義了相同的識別符號是沒有關係的,並不會產生衝突。尋找一個識別符號的值過程總是從當前層開始往上找的,首先找到的就為這個識別符號的值。也由此可以這麼說,'B'層識別符號在所有模組(.py檔案)中可用;'G'層識別符號在當前模組內(.py檔案)中可用;'E'和'L'層識別符號在當前函式內可用。

再來看一個例子,來解釋global語句的用法。程式碼如下所示:

1 >>> g = 'global'
2 >>> s = 'in'
3 >>> def out():
4     g = 'out'
5     def inter():
6      global g     
7      print s,g
8   inter()
9 >>> out() ===> 'in global'

可以看到,雖然有兩個層中的g,但使用了global語句後,就是指'G'層的識別符號。也就是第7行中的g,就是指第1行產生的那個g,值為'global'。

最後說一句,其實只要在程式設計的時候注意一下,不要使用相同的識別符號,基本上就可以避免任何與名稱空間相關的問題。還有就是在一個函式中儘量不要使用上層名稱空間中的識別符號,如果一定要用,也最好使用引數傳遞的方式進行,這樣有利於保持函式的獨立性。

相關文章