Python3 命名空間和作用域
命名空間
先看看官方文檔的一段話:
A namespace is a mapping from names to objects.Most namespaces are currently implemented as Python dictionaries。
命名空間(Namespace)是從名稱到對象的映射,大部分的命名空間都是通過 Python 字典來實現的。
命名空間提供了在專案中避免名字衝突的一種方法。各個命名空間是獨立的,沒有任何關係的,所以一個命名空間中不能有重名,但不同的命名空間是可以重名而沒有任何影響。
我們舉一個電腦系統中的例子,一個檔夾(目錄)中可以包含多個檔夾,每個檔夾中不能有相同的檔案名,但不同檔夾中的檔可以重名。
一般有三種命名空間:
- 內置名稱(built-in names), Python 語言內置的名稱,比如函數名 abs、char 和異常名稱 BaseException、Exception 等等。
- 全局名稱(global names),模組中定義的名稱,記錄了模組的變數,包括函數、類、其他導入的模組、模組級的變數和常量。
- 局部名稱(local names),函數中定義的名稱,記錄了函數的變數,包括函數的參數和局部定義的變數。(類中定義的也是)
命名空間查找順序:
假設我們要使用變數 zaixian,則 Python 的查找順序為:局部的命名空間去 -> 全局命名空間 -> 內置命名空間。
如果找不到變數 zaixian,它將放棄查找並引發一個 NameError 異常:
NameError: name 'zaixian' is not defined。
命名空間的生命週期:
命名空間的生命週期取決於對象的作用域,如果對象執行完成,則該命名空間的生命週期就結束。
因此,我們無法從外部命名空間訪問內部命名空間的對象。
實例
var1 = 5
def some_func():
# var2 是局部名稱
var2 = 6
def some_inner_func():
# var3 是內嵌的局部名稱
var3 = 7
如下圖所示,相同的對象名稱可以存在於多個命名空間中。
作用域
A scope is a textual region of a Python program where a namespace is directly accessible. "Directly accessible" here means that an unqualified reference to a name attempts to find the name in the namespace.
作用域就是一個 Python 程式可以直接訪問命名空間的正文區域。
在一個 python 程式中,直接訪問一個變數,會從內到外依次訪問所有的作用域直到找到,否則會報未定義的錯誤。
Python 中,程式的變數並不是在哪個位置都可以訪問的,訪問許可權決定於這個變數是在哪里賦值的。
變數的作用域決定了在哪一部分程式可以訪問哪個特定的變數名稱。Python的作用域一共有4種,分別是:
有四種作用域:
- L(Local):最內層,包含局部變數,比如一個函數/方法內部。
- E(Enclosing):包含了非局部(non-local)也非全局(non-global)的變數。比如兩個嵌套函數,一個函數(或類) A 裏面又包含了一個函數 B ,那麼對於 B 中的名稱來說 A 中的作用域就為 nonlocal。
- G(Global):當前腳本的最外層,比如當前模組的全局變數。
- B(Built-in): 包含了內建的變數/關鍵字等。,最後被搜索
規則順序: L –> E –> G –>gt; B。
在局部找不到,便會去局部外的局部找(例如閉包),再找不到就會去全局找,再者去內置中找。
g_count = 0 # 全局作用域 def outer(): o_count = 1 # 閉包函數外的函數中 def inner(): i_count = 2 # 局部作用域
內置作用域是通過一個名為 builtin 的標準模組來實現的,但是這個變數名自身並沒有放入內置作用域內,所以必須導入這個檔才能夠使用它。在Python3.0中,可以使用以下的代碼來查看到底預定義了哪些變數:
>>> import builtins >>> dir(builtins)
Python 中只有模組(module),類(class)以及函數(def、lambda)才會引入新的作用域,其他的代碼塊(如 if/elif/else/、try/except、for/while等)是不會引入新的作用域的,也就是說這些語句內定義的變數,外部也可以訪問,如下代碼:
>>> if True: ... msg = 'I am from zaixian' ... >>> msg 'I am from zaixian' >>>
實例中 msg 變數定義在 if 語句塊中,但外部還是可以訪問的。
如果將 msg 定義在函數中,則它就是局部變數,外部不能訪問:
>>> def test(): ... msg_inner = 'I am from zaixian' ... >>> msg_inner Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'msg_inner' is not defined >>>
從報錯的資訊上看,說明了 msg_inner 未定義,無法使用,因為它是局部變數,只有在函數內可以使用。
全局變數和局部變數
定義在函數內部的變數擁有一個局部作用域,定義在函數外的擁有全局作用域。
局部變數只能在其被聲明的函數內部訪問,而全局變數可以在整個程式範圍內訪問。調用函數時,所有在函數內聲明的變數名稱都將被加入到作用域中。如下實例:
實例(Python 3.0+)
以上實例輸出結果:
函數內是局部變數 : 30 函數外是全局變數 : 0
global 和 nonlocal關鍵字
當內部作用域想修改外部作用域的變數時,就要用到global和nonlocal關鍵字了。
以下實例修改全局變數 num:
實例(Python 3.0+)
以上實例輸出結果:
1 123 123
如果要修改嵌套作用域(enclosing 作用域,外層非全局作用域)中的變數則需要 nonlocal 關鍵字了,如下實例:
實例(Python 3.0+)
以上實例輸出結果:
100 100
另外有一種特殊情況,假設下麵這段代碼被運行:
實例(Python 3.0+)
以上程式執行,報錯資訊如下:
Traceback (most recent call last): File "test.py", line 7, in <module> test() File "test.py", line 5, in test a = a + 1 UnboundLocalError: local variable 'a' referenced before assignment
錯誤資訊為局部作用域引用錯誤,因為 test 函數中的 a 使用的是局部,未定義,無法修改。
修改 a 為全局變數,通過函數參數傳遞,可以正常執行輸出結果為:
實例(Python 3.0+)
執行輸出結果為: