命名空間定義了在某個(gè)作用域內(nèi)變量名和綁定值之間的對(duì)應(yīng)關(guān)系,命名空間是鍵值對(duì)的集合,變量名與值是一一對(duì)應(yīng)關(guān)系。作用域定義了命名空間中的變量能夠在多大范圍內(nèi)起作用。
命名空間在python解釋器中是以字典的形式存在的,是以一種可以看得見(jiàn)摸得著的實(shí)體存在的。作用域是python解釋器定義的一種規(guī)則,該規(guī)則確定了運(yùn)行時(shí)變量查找的順序,是一種形而上的虛的規(guī)定。
A namespace is a mapping from names to objects.Most namespaces are currently implemented as Python dictionaries。
命名空間是名字和對(duì)象的映射,命名空間是通過(guò) Python Dictionary(字典) 來(lái)實(shí)現(xiàn)的。
命名空間提供了一個(gè)在大型項(xiàng)目下避免名字沖突的方法
Python 中各個(gè)命名空間都是獨(dú)立的,他們之間無(wú)任何關(guān)系
一個(gè)命名空間中不能有重名,但不同的命名空間是可以重名而沒(méi)有任何影響。
命名空間就像是計(jì)算機(jī)中的文件夾一樣,同一個(gè)文件夾中的文件不可重名,但是如果兩個(gè)文件從屬于不同的文件夾就可以重名。
同理相同的對(duì)象名可以存在不同的命名空間中:
命名空間的種類(lèi)分為 3 類(lèi),命名空間的種類(lèi)也體現(xiàn)了命名空間的生命周期。三個(gè)種類(lèi)及生命周期描述如下:
1)內(nèi)置名稱(chēng)(built-in names)
Python 語(yǔ)言內(nèi)置的名稱(chēng),比如函數(shù)名 abs、char 和異常名稱(chēng) BaseException、Exception 等等。
生命周期:
對(duì)于Python built-in names組成的命名空間,它在Python解釋器啟動(dòng)的時(shí)候被創(chuàng)建,在解釋器退出的時(shí)候才被刪除;
2)全局名稱(chēng)(global names)
模塊中定義的名稱(chēng),記錄了模塊的變量,包括函數(shù)、類(lèi)、其它導(dǎo)入的模塊、模塊級(jí)的變量和常量。
生命周期:
對(duì)于一個(gè)Python模塊的global namespace,它在這個(gè)module被import的時(shí)候創(chuàng)建,在解釋器退出的時(shí)候退出;
3)局部名稱(chēng)(local names)
函數(shù)中定義的名稱(chēng),記錄了函數(shù)的變量,包括函數(shù)的參數(shù)和局部定義的變量。(類(lèi)中定義的也是)
生命周期:
對(duì)于一個(gè)函數(shù)的local namespace,它在函數(shù)每次被調(diào)用的時(shí)候創(chuàng)建,函數(shù)返回的時(shí)候被刪除。
注意:命名空間的生命周期取決于對(duì)象的作用域,如果對(duì)象執(zhí)行完成,則該命名空間的生命周期就結(jié)束。因此,我們無(wú)法從外部命名空間訪問(wèn)內(nèi)部命名空間的對(duì)象。例如:
# var1 是全局名稱(chēng)
var1 = 5
def some_func():
# var2 是局部名稱(chēng)
var2 = 6
def some_inner_func():
# var3 是內(nèi)嵌的局部名稱(chēng)
var3 = 7
命名空間分類(lèi)圖如下:
如果程序執(zhí)行時(shí)去使用一個(gè)變量 hello ,那么 Python, 查找變量順序?yàn)椋?/p>
局部的命名空間 -> 全局命名空間 -> 內(nèi)置命名空間
如果按照這個(gè)順序找不到相應(yīng)的變量,它將放棄查找并拋出一個(gè) NameError 異常:
NameError: name 'hello' is not defined。
python解釋器啟動(dòng) ->創(chuàng)建內(nèi)建命名空間 -> 加載模塊 -> 創(chuàng)建全局命名空間 ->函數(shù)被調(diào)用 ->創(chuàng)建局部命名空間
函數(shù)調(diào)用結(jié)束 -> 銷(xiāo)毀函數(shù)對(duì)應(yīng)的局部命名空間 -> python虛擬機(jī)(解釋器)退出 ->銷(xiāo)毀全局命名空間 ->銷(xiāo)毀內(nèi)建命名空間
一個(gè)模塊的引入,函數(shù)的調(diào)用,類(lèi)的定義都會(huì)引入命名空間,函數(shù)中的再定義函數(shù),類(lèi)中的成員函數(shù)定義會(huì)在局部namespace中再次引入局部namespace。
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.
作用域就是一個(gè) Python 程序可以直接訪問(wèn)命名空間的正文區(qū)域。
Python 程序中,直接訪問(wèn)一個(gè)變量,會(huì)從內(nèi)到外依次訪問(wèn)所有的作用域直到找到,否則會(huì)報(bào)未定義的錯(cuò)誤。
Python 中,程序的變量并不是在哪個(gè)位置都可以訪問(wèn)的,訪問(wèn)權(quán)限決定于這個(gè)變量是在哪里賦值的。
Python 中, 變量的作用域決定了在哪一部分程序可以訪問(wèn)哪個(gè)特定的變量名稱(chēng)
作用域分為4類(lèi),分別如下:
L(Local):最內(nèi)層,包含局部變量,比如一個(gè)函數(shù)/方法內(nèi)部。
E(Enclosing):包含了非局部(non-local)也非全局(non-global)的變量。比如兩個(gè)嵌套函數(shù),一個(gè)函數(shù)(或類(lèi)) A 里面又包含了一個(gè)函數(shù) B ,那么對(duì)于 B 中的名稱(chēng)來(lái)說(shuō) A 中的作用域就為 nonlocal。
G(Global):當(dāng)前腳本的最外層,比如當(dāng)前模塊的全局變量。
B(Built-in):包含了內(nèi)建的變量/關(guān)鍵字等,最后被搜索。
作用域規(guī)則順序?yàn)椋篖->E->G->B 如果變量在局部?jī)?nèi)找不到,便會(huì)去局部外的局部找(例如閉包),再找不到就會(huì)去全局找,再找不到就去內(nèi)置中找,如下圖所示:
局部作用域 (Local)是腳本中的最內(nèi)層,包含局部變量,比如一個(gè)函數(shù)或方法內(nèi)部。閉包函數(shù)外函數(shù)(Enclosing)包含了非局部(non-local)也非全局(non-global)的變量。全局作用域(Global)是當(dāng)前腳本的最外層,如當(dāng)前模塊的全局變量,實(shí)例如下:
global_scope = 0 # 全局作用域
# 定義閉包函數(shù)中的局部作用域
def outer():
o_count = 1 # 閉包函數(shù)外的函數(shù)中,相對(duì)于函數(shù) inner() 來(lái)說(shuō) 作用域非局部
def inner():
local_scope = 2 # 局部作用域
以上實(shí)例展示的是全局作用域和閉包函數(shù)中的函數(shù),以及函數(shù)中的局部作用域,對(duì)于函數(shù) inner() 來(lái)說(shuō),outer() 中的作用域?yàn)?non-local
Python 中的內(nèi)建作用域(Built-in):包含了內(nèi)建的變量/關(guān)鍵字等,最后被搜索
內(nèi)建作用域是通過(guò)一個(gè)名為 builtin 的標(biāo)準(zhǔn)模塊來(lái)實(shí)現(xiàn)的,但是這個(gè)變量名自身并沒(méi)有放入內(nèi)置作用域內(nèi),所以必須導(dǎo)入這個(gè)文件才能夠使用它。在Python3.0中,可以使用以下的代碼來(lái)查看到底預(yù)定義了哪些變量:
import builtins
dir(builtins)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError'...]
Python 中只有模塊(module),類(lèi)(class)以及函數(shù)(def、lambda)才會(huì)引入新的作用域,其它的代碼塊(如 if/elif/else/、try/except、for/while等)是不會(huì)引入新的作用域的,也就是說(shuō)這些語(yǔ)句內(nèi)定義的變量,外部也可以訪問(wèn),如下:
name1 = 'SuSan'
if chr('SuSan'.__eq__(name1)):
result = 'I am from China'
else:
result = 'I am from USA'
print(result)
# 輸出結(jié)果為:
I am SuSan,I am from China
實(shí)例中 result 變量定義在 if 語(yǔ)句塊中,但外部還是可以訪問(wèn)的。
如果將 result 定義在函數(shù)中,則它就是局部變量,外部不能訪問(wèn),在代碼中會(huì)報(bào)錯(cuò)運(yùn)行出異常:
# 如果將變量定義在函數(shù)內(nèi)部,則外部不能訪問(wèn)
def names():
name2 = 'SuSan'
# 在程序調(diào)用方法內(nèi)部的變量報(bào)錯(cuò)
if('SuSan'.__eq__(name2)):
result = 'I am '+name2 +','+'I am from China'
else:
result = 'I am from USA'
print(result)
#運(yùn)行輸出異常
Traceback (most recent call last):
File "python_scope.py", line 30, in <module>
if('SuSan'.__eq__(name1)):
NameError: name 'name2' is not defined
從以上報(bào)錯(cuò)信息看出,name2 未定義,因?yàn)閚ame2 是函數(shù)names() 中的局部變量,只能在函數(shù)內(nèi)部調(diào)用,外部不能調(diào)用函數(shù)中的局部變量。
全局變量:定義在函數(shù)外部擁有全局作用域的變量
局部變量:定義在函數(shù)內(nèi)部擁有局部作用域的變量
局部變量只能在其被聲明的函數(shù)內(nèi)部訪問(wèn),而全局變量可以在整個(gè)程序范圍內(nèi)訪問(wèn)。調(diào)用函數(shù)時(shí),所有在函數(shù)內(nèi)聲明的變量名稱(chēng)都將被加入到作用域中。如下實(shí)例:
# 全局變量和局部變量
total = 0 # 這是一個(gè)全局變量
# 函數(shù)說(shuō)明
def sum(arg1, arg2):
# 返回2個(gè)參數(shù)的和."
total = arg1 + arg2 # total在這里是局部變量.
print("函數(shù)內(nèi)是局部變量 : ", total)
return total
# 調(diào)用sum函數(shù),傳入?yún)?shù)的計(jì)算結(jié)果顯示局部變量
sum(10, 20)
print("函數(shù)外是全局變量 : ", total)
# 輸出結(jié)果為:
函數(shù)內(nèi)是局部變量 : 30
函數(shù)外是全局變量 : 0
當(dāng)內(nèi)部作用域想修改外部作用域的變量時(shí),就要用到global和nonlocal關(guān)鍵字了。
變量訪問(wèn)順序:
當(dāng)前作用域局部變量->外層作用域變量->再外層作用域變量->......->當(dāng)前模塊全局變量->pyhton內(nèi)置變量
global:全局變量,當(dāng)局部作用域改變?nèi)肿兞坑胓lobal,同時(shí)global還可以定義新的全局變量
nonlocal:外層嵌套函數(shù)的變量,nonlocal不能定義新的外層函數(shù)變量,只能改變已有的外層函數(shù)變量,同時(shí)nonlocal不能改變?nèi)肿兞?/span>
num = 1
def fun1():
# 申明訪問(wèn)全局變量
global num # 需要使用 global 關(guān)鍵字聲明
# 輸出全局變量原始值
print(num)
# 修改全局變量
num = 123
print(num)
# 調(diào)用函數(shù)
fun1()
# 輸出修改后的全局變量值
print(num)
以上實(shí)例輸出結(jié)果為:
1
123
123
如果要修改嵌套作用域(enclosing 作用域,外層非全局作用域)中的變量則需要 nonlocal 關(guān)鍵字
# 定義函數(shù)
def outer():
# 定義變量
num = 10
# 定義嵌套函數(shù)
def inner():
nonlocal num # nonlocal關(guān)鍵字聲明,使用函數(shù)中變量
# 修改變量值
num = 100
print(num)
inner()
print(num)
outer()
以上實(shí)例輸出:
100
100
另外還有一種特殊情況,以下這段代碼有語(yǔ)法錯(cuò)誤,運(yùn)行會(huì)報(bào)一個(gè)異常:
b = 8
def test():
b = b * 10
print(b)
test()
# 異常信息:UnboundLocalError
程序執(zhí)行異常:
Traceback (most recent call last):
File "python_scope.py", line 90, in <module>
test()
File "python_scope.py", line 88, in test
a = a + 1
UnboundLocalError: local variable 'a' referenced before assignment
錯(cuò)誤信息為局部作用域引用錯(cuò)誤,因?yàn)?test 函數(shù)中的 a 使用的是局部變量,未定義,無(wú)法修改。將 a 修改為全局變量,通過(guò)函數(shù)參數(shù)傳遞,程序就可以正常執(zhí)行,輸出結(jié)果為:
b = 8
def test(b):
b = b * 10
print(b)
test(b)
程序輸出結(jié)果為:
80
另一種解決辦法是加 global 關(guān)鍵字:
b = 8
def test():
global b
b = b * 30
print(b)
test()
輸出結(jié)果為:
240
兩者的功能不同。global關(guān)鍵字修飾變量后標(biāo)識(shí)該變量是全局變量,對(duì)該變量進(jìn)行修改就是修改全局變量,而nonlocal關(guān)鍵字修飾變量后標(biāo)識(shí)該變量是上一級(jí)函數(shù)中的局部變量,如果上一級(jí)函數(shù)中不存在該局部變量,nonlocal位置會(huì)發(fā)生錯(cuò)誤(最上層的函數(shù)使用nonlocal修飾變量必定會(huì)報(bào)錯(cuò))。
兩者使用的范圍不同。global關(guān)鍵字可以用在任何地方,包括最上層函數(shù)中和嵌套函數(shù)中,即使之前未定義該變量,global修飾后也可以直接使用,而nonlocal關(guān)鍵字只能用于嵌套函數(shù)中,并且外層函數(shù)中定義了相應(yīng)的局部變量,否則會(huì)發(fā)生錯(cuò)誤
本節(jié)給大家介紹了 Python 命名空間和作用用戶的介紹與簡(jiǎn)單應(yīng)用,在 Python 開(kāi)發(fā)實(shí)戰(zhàn)中對(duì)命名空間和作用域的運(yùn)用比較廣泛,謹(jǐn)以此文獻(xiàn)給在 Python 學(xué)習(xí)道路上的道友,希望對(duì)大家有一絲幫助。
參考:
https://www.runoob.com/python3/python3-namespace-scope.html
LeetCode面試系列 第4天:No.202 - 快樂(lè)數(shù)
聯(lián)系客服