五、變量的作用域
當(dāng)你在一個(gè)程序中使用變量名時(shí)渐行,Python創(chuàng)建轰坊、改變或查找變量名都是在命名空間(一個(gè)保存變量名的地方,這個(gè)地方的范圍也叫作變量的作用域)中進(jìn)行的祟印。
在創(chuàng)建變量時(shí)肴沫,Python將變量名被創(chuàng)建的地點(diǎn)關(guān)聯(lián)給(綁定給)一個(gè)特定的命名空間。也就是說在代碼中變量創(chuàng)建的位置決定了這個(gè)變量將存在于哪個(gè)命名空間蕴忆,也就是它可以被訪問的范圍颤芬。
函數(shù)的作用域有助于防止程序之中變量名的沖突,并且有助于函數(shù)成為更加獨(dú)立的程序單元套鹅。
1站蝠、作用域
變量的作用域可以分為:本地作用域、全局作用域和內(nèi)置作用域卓鹿。
在任何情況下菱魔,一個(gè)變量名的作用域總是由變量在程序中被創(chuàng)建的位置所決定的,并且與函數(shù)被調(diào)用的地點(diǎn)完全沒有關(guān)系吟孙。
- 如果一個(gè)變量在函數(shù)內(nèi)創(chuàng)建澜倦,它被定位在這個(gè)函數(shù)之內(nèi),那么他的作用于就是本地的杰妓。
- 如果一個(gè)變量在一個(gè)嵌套的函數(shù)內(nèi)創(chuàng)建藻治,對于外層的函數(shù)來說,它是非本地的(也就是外層函數(shù)式無法訪問的)巷挥。
- 如果一個(gè)變量在函數(shù)之外(也就是在Python的頂級代碼快中)創(chuàng)建桩卵,它他的作用域就是全局的。
(1) 本地作用域
在一個(gè)函數(shù)內(nèi)部對一個(gè)變量名(不包含變量成員的引用,例如:name[1]等)任何類型的賦值(而不是在一個(gè)表達(dá)式中對其進(jìn)行引用)都會創(chuàng)建新的變量吸占,并把新創(chuàng)建的變量劃定為本地的作用域晴叨。這包括 = 語句、import 語句矾屯、def 語句兼蕊、函數(shù)參數(shù)名稱等。
在默認(rèn)的情況下件蚕,函數(shù)內(nèi)創(chuàng)建的所有變量都是與函數(shù)的本地命名空間相關(guān)聯(lián)的孙技。這意味著:一個(gè)在 def 內(nèi)定義的變量能夠被 def 內(nèi)的代碼使用,不能在函數(shù)的外部被引用排作。當(dāng)在函數(shù)之外給一個(gè)變量名賦值時(shí)(也就是牵啦,在一個(gè)模塊文件的頂層,或者是在交互提示模式下)妄痪,本地作用域與全局作用域(這個(gè)模塊的命名空間)是相同的哈雏。
本地變量作為臨時(shí)的變量名,只有在函數(shù)運(yùn)行時(shí)才需要他們衫生。
嵌套作用域
Python 為嵌套函數(shù)提供了嵌套的命名空間(作用域)裳瘪,使得嵌套在內(nèi)部的函數(shù)內(nèi)創(chuàng)建的變量名本地化,以便嵌套函數(shù)內(nèi)部使用的變量名不會與嵌套函數(shù)外的變量名產(chǎn)生沖突罪针。閉合函數(shù)
嵌套作用域的查找在嵌套的函數(shù)已經(jīng)返回后也是有效的彭羹。這種行為有時(shí)也叫作閉合(closure)函數(shù) —— 一個(gè)能夠記住嵌套作用域的變量值的函數(shù),盡管那個(gè)作用域或許已經(jīng)不存在了泪酱。這是一種相當(dāng)高級的技術(shù)派殷,除了那些擁有函數(shù)式編程背景的程序員們,以后在實(shí)際使用中并不常見墓阀。另一方面毡惜,嵌套的作用域常常被 lambda 函數(shù)創(chuàng)建表達(dá)式使用——因?yàn)樗麄兪潜磉_(dá)式,它們幾乎總是嵌套在一個(gè)函數(shù)中斯撮。此外虱黄,函數(shù)嵌套通常用作裝飾器 —— 在某些情況下,它是最合理的編碼模式吮成。
通常來說,類是一個(gè)更好的像這樣“記憶”的選擇辜梳,因?yàn)樗鼈冏尃顟B(tài)變得很明確粱甫。不使用類的話,全局變量作瞄、像這樣的嵌套作用域引用以及默認(rèn)的參數(shù)就是 Python 的函數(shù)能夠保留狀態(tài)信息的主要方法了茶宵。
在 Python 中作用域是可以做任意的嵌套的,但是我們應(yīng)當(dāng)盡量少的定義嵌套函數(shù)宗挥。
(2)全局作用域
函數(shù)定義了本地作用域乌庶,而模塊定義的是全局作用域种蝶。每個(gè)模塊都是一個(gè)全局作用域。
全局作用域的作用范圍僅限于單個(gè)文件瞒大。這里的全局指的是在一個(gè)文件的頂層創(chuàng)建的變量名僅對于這個(gè)文件內(nèi)部的代碼而言是全局的螃征。在 Python 中沒有基于一個(gè)單個(gè)文件的并可以應(yīng)用在任何其他文件的全局作用域。
全局變量的一個(gè)特征是除非被刪除掉透敌,否則它們將存活到文件運(yùn)行結(jié)束盯滚,且對于所有的函數(shù),他們的值都是可以被訪問的酗电。然而局部變量魄藕,就像它們存放的棧,只是在函數(shù)調(diào)用時(shí)暫時(shí)地存在撵术,僅僅只依賴于定義它們的函數(shù)現(xiàn)階段是否處于活動(dòng)狀態(tài)背率。
(3)內(nèi)置作用域
內(nèi)置作用域僅僅是一個(gè)名為 builtins 的內(nèi)置模塊,必須要 import builtins 之后才能使用這個(gè)模塊嫩与,因?yàn)樽兞棵?builtins 本身并沒有預(yù)先導(dǎo)入寝姿。上面圖片中列表中的變量名組成了 Python 中的內(nèi)置作用域。前一半是內(nèi)置的異常蕴纳,后一半是內(nèi)置函數(shù)会油。由于下面要講的 LEGB 法則最后將自動(dòng)搜索這個(gè)模塊,將會自動(dòng)得到這個(gè)列表中的所有變量古毛。所以你能夠使用這些變量而不需要導(dǎo)入 builtins 模塊翻翩。
2、LEGB法則
Python的變量名解析機(jī)制稱為 LEGB 法則稻薇,這也是由作用域的命名而來的嫂冻。
當(dāng)在函數(shù)中使用變量時(shí),Python搜索4個(gè) 作用域:本地作用域(L)塞椎、之后是上一層結(jié)構(gòu)中的 def 或 lambda 的本地作用域(E)桨仿,之后是全局作用域(G),最后是內(nèi)置作用域(B)案狠。并且在第一處能夠找到這個(gè)變量名的地方停下來服傍。如果變量名在這次搜索中沒有找到,Python 會拋出 NameError 異常骂铁。3吹零、global 語句
在默認(rèn)情況下,所有在一個(gè)函數(shù)中被賦值的變量都位于這個(gè)函數(shù)的本地作用域拉庵,并且僅在這個(gè)函數(shù)運(yùn)行的過程中存在灿椅。為了在函數(shù)內(nèi)創(chuàng)建或修改一個(gè)全局作用域的變量,需要使用 global 語句來聲明使用全局作用域。
global 語句是一個(gè)使用全局命名空間的聲明茫蛹,它告訴 Python 函數(shù)打算生成或修改一個(gè)或多個(gè)全局變量名操刀。global 使得作用域查找跳過本地作用域從全局作用域開始,如果變量不存在將繼續(xù)到內(nèi)置作用域婴洼。但是骨坑,對變量的賦值總是在全局作用域中創(chuàng)建或修改它們。
global 語句包含了關(guān)鍵字 global窃蹋,其后跟著一個(gè)或多個(gè)由逗號分開的變量名卡啰。當(dāng)函數(shù)主體調(diào)用時(shí),所有列出來的變量名將被映射到全局作用域內(nèi)警没。最小化全局變量
在默認(rèn)情況下匈辱,函數(shù)內(nèi)部注冊的變量名是本地變量,這是有意而為之的杀迹。將其改為全局變量會引發(fā)一些軟件問題:由于變量的值取決于函數(shù)調(diào)用的順序亡脸,而函數(shù)自身是任意順序進(jìn)行排列的,導(dǎo)致了程序調(diào)試起來變得很困難树酪。另一方面浅碾,不使用面向?qū)ο蟮木幊谭椒ㄒ约邦惖脑挘肿兞恳苍S就是Python中最直接保持全局狀態(tài)信息的方法(函數(shù)在下次被調(diào)用時(shí)需記住的信息):本地變量在函數(shù)返回時(shí)將會消失续语,而全局變量不是這樣垂谢。
在不熟悉編程的情況下,最好盡可能的避免使用全局變量疮茄。
最小化文件間的修改
盡管我們能夠直接修改另一個(gè)文件中的變量滥朱,但是往往我們都不這樣做。一個(gè)模塊文件的全局變量一旦被導(dǎo)入就成為了這個(gè)模塊對象的一個(gè)屬性:導(dǎo)入者自動(dòng)得到了這個(gè)被導(dǎo)入的模塊文件的所有全局變量的訪問權(quán)力试。
這會讓兩個(gè)文件有過強(qiáng)的相關(guān)性:假使它們都與變量X的值相關(guān)徙邻,如果沒有其中一個(gè)文件的話很難理解或重用另一個(gè)文件。這樣隱含的跨文件依賴性畸裳,在最好的情況下會導(dǎo)致代碼不靈活缰犁,最壞的情況會引發(fā) bug。
最好的解決辦法就是別這樣做:在文件間進(jìn)行通信最好的辦法就是通過調(diào)用函數(shù)怖糊,傳遞參數(shù)帅容,然后得到其返回值。
4伍伤、nonlocal 語句
如果需要在嵌套函數(shù)的內(nèi)層函數(shù)中直接使用外層函數(shù)中的變量丰嘉,可以使用 nonlocal 語句來做到。
nonlocal 應(yīng)用于嵌套內(nèi)層的函數(shù)作用域中的變量名嚷缭,而且在聲明 nonlocal 名稱的時(shí)候,他聲明的變量必須已經(jīng)存在于外層嵌套函數(shù)的作用域中 。這就允許封閉的函數(shù)作為保留狀態(tài)的一個(gè)地方——當(dāng)一個(gè)函數(shù)調(diào)用的時(shí)候阅爽,信息被記住了——而不必使用共享的全局名稱路幸。
nonlocal 語句還加快了引用——就像 global 語句一樣,nonlocal 使得對該語句中列出的名稱的查找從嵌套的外層函數(shù)的作用域中開始付翁,而不是從所在函數(shù)的本地作用域開始简肴。也就是說,nonlocal 名稱只能出現(xiàn)在嵌套外層函數(shù)中百侧,作用域查找不會繼續(xù)到全局作用域或內(nèi)置作用域砰识。
當(dāng)執(zhí)行一條 nonlocal 語句時(shí),nonlocal 名稱必須已經(jīng)在一個(gè)嵌套的外層函數(shù)的作用域中創(chuàng)建佣渴,否則將會得到一個(gè)錯(cuò)誤——不能通過在嵌套的內(nèi)層函數(shù)的作用域中賦給它們一個(gè)新值來創(chuàng)建它們辫狼。
《Python基礎(chǔ)手冊》系列:
Python基礎(chǔ)手冊 1 —— Python語言介紹
Python基礎(chǔ)手冊 2 —— Python 環(huán)境搭建(Linux)
Python基礎(chǔ)手冊 3 —— Python解釋器
Python基礎(chǔ)手冊 4 —— 文本結(jié)構(gòu)
Python基礎(chǔ)手冊 5 —— 標(biāo)識符和關(guān)鍵字
Python基礎(chǔ)手冊 6 —— 操作符
Python基礎(chǔ)手冊 7 —— 內(nèi)建函數(shù)
Python基礎(chǔ)手冊 8 —— Python對象
Python基礎(chǔ)手冊 9 —— 數(shù)字類型
Python基礎(chǔ)手冊10 —— 序列(字符串)
Python基礎(chǔ)手冊11 —— 序列(元組&列表)
Python基礎(chǔ)手冊12 —— 序列(類型操作)
Python基礎(chǔ)手冊13 —— 映射(字典)
Python基礎(chǔ)手冊14 —— 集合
Python基礎(chǔ)手冊15 —— 解析
Python基礎(chǔ)手冊16 —— 文件
Python基礎(chǔ)手冊17 —— 簡單語句
Python基礎(chǔ)手冊18 —— 復(fù)合語句(流程控制語句)
Python基礎(chǔ)手冊19 —— 迭代器
Python基礎(chǔ)手冊20 —— 生成器
Python基礎(chǔ)手冊21 —— 函數(shù)的定義
Python基礎(chǔ)手冊22 —— 函數(shù)的參數(shù)
Python基礎(chǔ)手冊23 —— 函數(shù)的調(diào)用
Python基礎(chǔ)手冊24 —— 函數(shù)中變量的作用域
Python基礎(chǔ)手冊25 —— 裝飾器
Python基礎(chǔ)手冊26 —— 錯(cuò)誤 & 異常
Python基礎(chǔ)手冊27 —— 模塊
Python基礎(chǔ)手冊28 —— 模塊的高級概念
Python基礎(chǔ)手冊29 —— 包