1. 程式人生 > >解讀Python程式設計中的名稱空間與作用域

解讀Python程式設計中的名稱空間與作用域

變數是擁有匹配物件的名字(識別符號)。名稱空間是一個包含了變數名稱們(鍵)和它們各自相應的物件們(值)的字典。
一個Python表示式可以訪問區域性名稱空間和全域性名稱空間裡的變數。如果一個區域性變數和一個全域性變數重名,則區域性變數會覆蓋全域性變數。
每個函式都有自己的名稱空間。類的方法的作用域規則和通常函式的一樣。
Python會智慧地猜測一個變數是區域性的還是全域性的,它假設任何在函式內賦值的變數都是區域性的。
因此,如果要給全域性變數在一個函式裡賦值,必須使用global語句。
global VarName的表示式會告訴Python, VarName是一個全域性變數,這樣Python就不會在區域性名稱空間裡尋找這個變量了。

名稱空間的定義
Python名稱空間是名稱到物件的對映,這就像是字典,鍵名是變數名,值是變數的值。比如:    
>>> x = 3
>>> globals()
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', '__doc__': None, 'x': 3, '__package__': None}

可以看到變數x,3以字典的形式存放在globals空間內。以之對應的名字空間還有:locals()。  
>>> locals()
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', '__doc__': None, 'x': 3, '__package__': None}

實際上,你可以通過向名字新增鍵名和值:
    
>>> globals()['y'] = 5
>>> y
5

上圖左側是內建名稱空間,右側是不同的模組,有各自的全域性名稱空間,全域性名稱空間內定義函式就會有區域性名稱空間。

名稱空間的種類
Python中有三種名稱空間:

a) 區域性,函式內的名稱空間就是區域性的;
b) 全域性,模組內的名稱空間就是全域性的;

c) 內建,包括異常型別、內建函式和特殊方法,可以程式碼中任意地方呼叫;
下面討論關於名字空間的搜尋順序,先來看張圖:

https://img2.mukewang.com/5af99a370001f46803670254.jpg

名稱空間的可見性(作用域)

a) 內建名稱空間在程式碼所有位置都是可見的,所以可以隨時被呼叫;

b) 全域性名稱空間和區域性名稱空間中, 如果有同名變數,在全域性名稱空間處,區域性名稱空間內的同名變數是不可見的;

c) 在區域性名稱空間處,全域性名稱空間的同名變數是不可見的(只有變數不同名的情況下,可使用 global關鍵字讓其可見)。

知道了可見性,下面說變數的查詢順序就要清楚多了。


名稱空間的查詢順序
a) 如果在函式內呼叫一個變數,先在函式內(區域性名稱空間)查詢,如果找到則停止查詢。否則在函式外部(全域性名稱空間)查詢,如果還是沒找到,則查詢內建名稱空間。如果以上三個命名都未找到,則丟擲NameError 的異常錯誤。
b) 如果在函式外呼叫一個變數,則在函式外查詢(全域性名稱空間,區域性名稱空間此時不可見),如果找到則停止查詢,否則到內建名稱空間中查詢。如果兩者都找不到,則丟擲異常。只有當局部名稱空間內,使用global 關鍵字聲明瞭一個變數時,查詢順序則是 a) 的查詢順序。


為了幫助理解,來舉個例子,我們在全域性名稱空間裡定義一個變數money。我們再在函式內給變數money賦值,然後Python會假定money是一個區域性變數。然而,我們並沒有在訪問前宣告一個區域性變數money,結果就是會出現一個UnboundLocalError的錯誤。取消global語句的註釋就能解決這個問題。

#!/usr/bin/python
# -*- coding: UTF-8 -*-
  
Money = 2000
def AddMoney():
  # 想改正程式碼就取消以下注釋:
  # global Money
  Money = Money + 1
  
print Money
AddMoney()
print Money

dir()函式
dir()函式一個排好序的字串列表,內容是一個模組裡定義過的名字。
返回的列表容納了在一個模組裡定義的所有模組,變數和函式。如下一個簡單的例項:    
#!/usr/bin/python
# -*- coding: UTF-8 -*-
  
# 匯入內建math模組
import math
  
content = dir(math)
  
print content;

以上例項輸出結果:    
['__doc__', '__file__', '__name__', 'acos', 'asin', 'atan', 
'atan2', 'ceil', 'cos', 'cosh', 'degrees', 'e', 'exp', 
'fabs', 'floor', 'fmod', 'frexp', 'hypot', 'ldexp', 'log',
'log10', 'modf', 'pi', 'pow', 'radians', 'sin', 'sinh', 
'sqrt', 'tan', 'tanh']

在這裡,特殊字串變數__name__指向模組的名字,__file__指向該模組的匯入檔名。

globals()和locals()函式

    根據呼叫地方的不同,globals()和locals()函式可被用來返回全域性和區域性名稱空間裡的名字。
    如果在函式內部呼叫locals(),返回的是所有能在該函式裡訪問的命名。
    如果在函式內部呼叫globals(),返回的是所有在該函式裡能訪問的全域性名字。
    兩個函式的返回型別都是字典。所以名字們能用keys()函式摘取。

reload()函式
當一個模組被匯入到一個指令碼,模組頂層部分的程式碼只會被執行一次。
因此,如果你想重新執行模組裡頂層部分的程式碼,可以用reload()函式。該函式會重新匯入之前匯入過的模組。語法如下:    
reload(module_name)廈工叉車
在這裡,module_name要直接放模組的名字,而不是一個字串形式。比如想過載hello模組,如下:    
reload(hello)