Python命名空間和作用域淺析
Python3 命名空間和作用域
命名空間
先看看官方文檔的一段話:
A namespace is a mapping from names to objects.Most namespaces are currently implemented as Python dictionaries。
命名空間(Namespace)是從名稱到對象的映射,像是一個dict,key是變量的名字,value是變量的值。大部分的命名空間都是通過 Python 字典來實現的
(__dict__)。
命名空間提供了在項目中避免名字沖突的一種方法。各個命名空間是獨立的,沒有任何關系的,所以一個命名空間中不能有重名,但不同的命名空間是可以重名而沒有任何影響。
我們舉一個計算機系統中的例子,一個文件夾(目錄)中可以包含多個文件夾,每個文件夾中不能有相同的文件名,但不同文件夾中的文件可以重名。
一般有三種命名空間:
內置名稱(built-in names), Python 語言內置的名稱,比如函數名 abs、char 和異常名稱 BaseException、Exception 等等。
全局名稱(global names),模塊中定義的名稱,記錄了模塊的變量,包括函數、類、其它導入的模塊、模塊級的變量和常量。
局部名稱(local names),函數中定義的名稱,記錄了函數的變量,包括函數的參數和局部定義的變量。(類中定義的也是)
命名空間查找順序:
假設我們要使用變量 runoob,則 Python 的查找順序為:局部的命名空間去 -> 全局命名空間 -> 內置命名空間。
如果找不到變量 runoob,它將放棄查找并引發一個 NameError 異常:
NameError: name 'runoob' is not defined。
對于閉包來說,這里有一點區別,如果在local namespace中找不到變量的話,還會去父函數的local namespace中找變量。
命名空間的生命周期:
命名空間的生命周期取決于對象的作用域,如果對象執行完成,則該命名空間的生命周期就結束。
因此,我們無法從外部命名空間訪問內部命名空間的對象。
我們可以使用locals() globals()? ?__dict__訪問命名空間【locals()和globals()有一個區別是,locals只讀,globals可以寫】
import sys m =sys.modules[__name__] #sys.modules獲取了當前運行的模塊 def f(x=1,y=2): m=3 print(locals()) #獲取當前的命名空間(函數體內即為函數內部的命名空間) return 0 f() print(m.__dict__) #__dict__成員獲取了當前模塊的命名空間 print(locals()) #獲取當前的命名空間(全局) print(globals()) #獲取全局的命名空間
打印結果
{'x': 1, 'y': 2, 'm': 3} {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000001A2B0FA4188>, '__spec__': None, '__annotations__': {}, '__builtins__':
【解釋】:遇到一個名字的時候,Python解釋器首先會去本地命名空間(Local)中查找,然后再去其所在函數的作用域(Enclosing Function)中查找,如果還沒找到,就去全局命名空間(Global)中查找,最后會去__builtin__這個模塊中查找,__builtin__模塊在 Python3 中已經重命名成了builtins
【附】
from module import 和 import module
使用import module時,module本身被引入,但是保存它原有的命名空間,所以我們需要使用module.name這種方式訪問它的 函數和變量。
from module import這種方式,是將其它模塊的函數或者變量引到當前的命名空間中,所以就不需要使用module.name這種方式訪問其它的模塊的方法了。
if __name__ trick
python中的module也是對象,所有的modules都有一個內置的屬性__name__,模塊的__name__屬性的值取決于如何使用這個模塊,如果import module,那么__name__屬性的值是模塊的名字。如果直接執行這個模塊的話,那么__name__屬性的值就是默認值__main__。
module的一些內置屬性
__name__: 上面已經介紹過
__file__ : 當前module的絕對路徑
__dict__:
__doc__ :
__package__:
__path__:
作用域
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。
在局部找不到,便會去局部外的局部找(例如閉包),再找不到就會去全局找,再者去內置中找。
實戰分析:
x = 1234 def test(): print(x) x = 'abc' test()
在上面的函數中,我們要打印一個名字x,它首先會去本地命名空間中查找,沒有找到。然后去當前函數test的作用域中查找,找到了。此時Python解釋器就會發現x這個名字還沒有添加到local本地命名空間中,就被引用了。所以就會拋出一個異常,說x還未被賦值就被引用了。
import dis x = 1234 def test_right(): print(x) dis.dis(test_right) print('-' * 20) x = 1234 def test_error(): print(x) x = 'abc' dis.dis(test_error)
5 0 LOAD_GLOBAL 0 (print) 3 LOAD_GLOBAL 1 (x) 6 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 9 POP_TOP 10 LOAD_CONST 0 (None) 13 RETURN_VALUE -------------------- 13 0 LOAD_GLOBAL 0 (print) 3 LOAD_FAST 0 (x) 6 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 9 POP_TOP 14 10 LOAD_CONST 1 ('abc') 13 STORE_FAST 0 (x) 16 LOAD_CONST 0 (None) 19 RETURN_VALUE
通過查看這兩個函數的反匯編出來的代碼可以看到,這兩個函數訪問x的時候,一個是通過LOAD_GLOBAL指令,訪問的全局命名空間,另外一個則是通過LOAD_FAST指令訪問的本地命名空間。
Python
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。