這篇主要是偏理論的文章曙咽,可能會(huì)讀著有些乏味,但是在開發(fā)過程中涤浇,卻有著舉足輕重的地位鳖藕,很多無法排出的BUG很有可能就與此相關(guān)。
1只锭、概述
A namespace is a mapping from names to objects.Most namespaces are currently implemented as Python dictionaries著恩。
命名空間是名字和對(duì)象的映射,命名空間是通過 Python Dictionary(字典) 來實(shí)現(xiàn)的蜻展。
命名空間提供了一個(gè)在大型項(xiàng)目下避免名字沖突的方法
Python 中各個(gè)命名空間都是獨(dú)立的喉誊,他們之間無任何關(guān)系
一個(gè)命名空間中不能有重名,但不同的命名空間是可以重名而沒有任何影響纵顾。
同理相同的對(duì)象名可以存在不同的命名空間中:
2敷矫、命名空間種類
命名空間的種類分為 3 類,命名空間的種類也體現(xiàn)了命名空間的生命周期汉额。三個(gè)種類及生命周期描述如下:
1)內(nèi)置名稱(built-in names)
Python 語言內(nèi)置的名稱曹仗,比如函數(shù)名 abs、char 和異常名稱 BaseException蠕搜、Exception 等等怎茫。
生命周期:
對(duì)于Python built-in names組成的命名空間,它在Python解釋器啟動(dòng)的時(shí)候被創(chuàng)建讥脐,在解釋器退出的時(shí)候才被刪除遭居;
2)全局名稱(global names)
模塊中定義的名稱,記錄了模塊的變量旬渠,包括函數(shù)俱萍、類、其它導(dǎo)入的模塊告丢、模塊級(jí)的變量和常量枪蘑。
生命周期:
對(duì)于一個(gè)Python模塊的global namespace损谦,它在這個(gè)module被import的時(shí)候創(chuàng)建,在解釋器退出的時(shí)候退出岳颇;
3)局部名稱(local names)
函數(shù)中定義的名稱照捡,記錄了函數(shù)的變量,包括函數(shù)的參數(shù)和局部定義的變量话侧。(類中定義的也是)
生命周期:
對(duì)于一個(gè)函數(shù)的local namespace栗精,它在函數(shù)每次被調(diào)用的時(shí)候創(chuàng)建,函數(shù)返回的時(shí)候被刪除瞻鹏。
注意:命名空間的生命周期取決于對(duì)象的作用域悲立,如果對(duì)象執(zhí)行完成,則該命名空間的生命周期就結(jié)束新博。因此薪夕,我們無法從外部命名空間訪問內(nèi)部命名空間的對(duì)象。例如:
命名空間分類圖如下:
3赫悄、命名空間查找原献、創(chuàng)建、銷毀順序
3.1 查找變量
如果程序執(zhí)行時(shí)去使用一個(gè)變量 hello 埂淮,那么 Python姑隅, 查找變量順序?yàn)椋?/p>
局部的命名空間 -> 全局命名空間 -> 內(nèi)置命名空間
如果按照這個(gè)順序找不到相應(yīng)的變量,它將放棄查找并拋出一個(gè) NameError 異常:
3.2 各命名空間創(chuàng)建順序:
python解釋器啟動(dòng) ->創(chuàng)建內(nèi)建命名空間 -> 加載模塊 -> 創(chuàng)建全局命名空間 ->函數(shù)被調(diào)用 ->創(chuàng)建局部命名空間
3.3 各命名空間銷毀順序:
函數(shù)調(diào)用結(jié)束 -> 銷毀函數(shù)對(duì)應(yīng)的局部命名空間 -> python虛擬機(jī)(解釋器)退出 ->銷毀全局命名空間 ->銷毀內(nèi)建命名空間
4同诫、命名空間總結(jié)
一個(gè)模塊的引入粤策,函數(shù)的調(diào)用,類的定義都會(huì)引入命名空間误窖,函數(shù)中的再定義函數(shù)叮盘,類中的成員函數(shù)定義會(huì)在局部namespace中再次引入局部namespace。
二霹俺、作用域
1柔吼、概述
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 程序可以直接訪問命名空間的正文區(qū)域。
Python 程序中丙唧,直接訪問一個(gè)變量愈魏,會(huì)從內(nèi)到外依次訪問所有的作用域直到找到,否則會(huì)報(bào)未定義的錯(cuò)誤想际。
Python 中培漏,程序的變量并不是在哪個(gè)位置都可以訪問的,訪問權(quán)限決定于這個(gè)變量是在哪里賦值的胡本。
Python 中牌柄, 變量的作用域決定了在哪一部分程序可以訪問哪個(gè)特定的變量名稱
2、作用域種類
作用域分為4類侧甫,分別如下:
L(Local):最內(nèi)層珊佣,包含局部變量蹋宦,比如一個(gè)函數(shù)/方法內(nèi)部。
E(Enclosing):包含了非局部(non-local)也非全局(non-global)的變量咒锻。比如兩個(gè)嵌套函數(shù)冷冗,一個(gè)函數(shù)(或類) A 里面又包含了一個(gè)函數(shù) B ,那么對(duì)于 B 中的名稱來說 A 中的作用域就為 nonlocal惑艇。
G(Global):當(dāng)前腳本的最外層蒿辙,比如當(dāng)前模塊的全局變量。
B(Built-in):包含了內(nèi)建的變量/關(guān)鍵字等滨巴,最后被搜索须板。
作用域規(guī)則順序?yàn)椋篖->E->G->B 如果變量在局部內(nèi)找不到,便會(huì)去局部外的局部找(例如閉包)兢卵,再找不到就會(huì)去全局找,再找不到就去內(nèi)置中找绪颖,如下圖所示:
3秽荤、全局作用域和局部作用域
局部作用域 (Local)是腳本中的最內(nèi)層,包含局部變量柠横,比如一個(gè)函數(shù)或方法內(nèi)部窃款。閉包函數(shù)外函數(shù)(Enclosing)包含了非局部(non-local)也非全局(non-global)的變量。全局作用域(Global)是當(dāng)前腳本的最外層牍氛,如當(dāng)前模塊的全局變量晨继,實(shí)例如下:
以上實(shí)例展示的是全局作用域和閉包函數(shù)中的函數(shù),以及函數(shù)中的局部作用域搬俊,對(duì)于函數(shù) inner() 來說紊扬,outer() 中的作用域?yàn)?non-local
4、內(nèi)建作用域
Python 中的內(nèi)建作用域(Built-in):包含了內(nèi)建的變量/關(guān)鍵字等唉擂,最后被搜索
內(nèi)建作用域是通過一個(gè)名為 builtin 的標(biāo)準(zhǔn)模塊來實(shí)現(xiàn)的餐屎,但是這個(gè)變量名自身并沒有放入內(nèi)置作用域內(nèi),所以必須導(dǎo)入這個(gè)文件才能夠使用它玩祟。在Python3.0中腹缩,可以使用以下的代碼來查看到底預(yù)定義了哪些變量:
Python 中只有模塊(module),類(class)以及函數(shù)(def空扎、lambda)才會(huì)引入新的作用域藏鹊,其它的代碼塊(如 if/elif/else/、try/except转锈、for/while等)是不會(huì)引入新的作用域的盘寡,也就是說這些語句內(nèi)定義的變量,外部也可以訪問黑忱,如下:
實(shí)例中 result 變量定義在 if 語句塊中宴抚,但外部還是可以訪問的勒魔。
如果將 result 定義在函數(shù)中,則它就是局部變量菇曲,外部不能訪問冠绢,在代碼中會(huì)報(bào)錯(cuò)運(yùn)行出異常:
從以上報(bào)錯(cuò)信息看出,name2 未定義常潮,因?yàn)閚ame2 是函數(shù)names() 中的局部變量弟胀,只能在函數(shù)內(nèi)部調(diào)用,外部不能調(diào)用函數(shù)中的局部變量喊式。
5孵户、全局變量和局部變量
全局變量:定義在函數(shù)外部擁有全局作用域的變量
局部變量:定義在函數(shù)內(nèi)部擁有局部作用域的變量
局部變量只能在其被聲明的函數(shù)內(nèi)部訪問,而全局變量可以在整個(gè)程序范圍內(nèi)訪問岔留。調(diào)用函數(shù)時(shí)夏哭,所有在函數(shù)內(nèi)聲明的變量名稱都將被加入到作用域中。如下實(shí)例:
6献联、global 和 nonlocal關(guān)鍵字
當(dāng)內(nèi)部作用域想修改外部作用域的變量時(shí)竖配,就要用到global和nonlocal關(guā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)肿兞?/p>
6.1 修改全局變量
以上實(shí)例輸出結(jié)果為:
6.2 修改嵌套作用域
如果要修改嵌套作用域(enclosing 作用域,外層非全局作用域)中的變量則需要 nonlocal 關(guān)鍵字
以上實(shí)例輸出:
另外還有一種特殊情況原押,以下這段代碼有語法錯(cuò)誤胁镐,運(yùn)行會(huì)報(bào)一個(gè)異常:
程序執(zhí)行異常:
錯(cuò)誤信息為局部作用域引用錯(cuò)誤,因?yàn)?test 函數(shù)中的 a 使用的是局部變量诸衔,未定義盯漂,無法修改。將 a 修改為全局變量笨农,通過函數(shù)參數(shù)傳遞宠能,程序就可以正常執(zhí)行,輸出結(jié)果為:
程序輸出結(jié)果為:
另一種解決辦法是加 global 關(guān)鍵字:
輸出結(jié)果為:
6.3 global 和 nonlocal 的區(qū)別
兩者的功能不同磁餐。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é)
本節(jié)給大家介紹了 Python 命名空間和作用用戶的介紹與簡單應(yīng)用巩步,在 Python 開發(fā)實(shí)戰(zhàn)中對(duì)命名空間和作用域的運(yùn)用比較廣泛旁赊,謹(jǐn)以此文獻(xiàn)給在 Python 學(xué)習(xí)道路上的道友,希望對(duì)大家有一絲幫助椅野。