Python基礎(chǔ)手冊(cè)24——函數(shù)中變量的作用域

五、變量的作用域

當(dāng)你在一個(gè)程序中使用變量名時(shí),Python創(chuàng)建、改變或查找變量名都是在命名空間(一個(gè)保存變量名的地方,這個(gè)地方的范圍也叫作變量的作用域)中進(jìn)行的。

在創(chuàng)建變量時(shí),Python將變量名被創(chuàng)建的地點(diǎn)關(guān)聯(lián)給(綁定給)一個(gè)特定的命名空間。也就是說(shuō)在代碼中變量創(chuàng)建的位置決定了這個(gè)變量將存在于哪個(gè)命名空間,也就是它可以被訪問(wèn)的范圍。

函數(shù)的作用域有助于防止程序之中變量名的沖突,并且有助于函數(shù)成為更加獨(dú)立的程序單元。


1、作用域

變量的作用域可以分為:本地作用域、全局作用域和內(nèi)置作用域。

在任何情況下,一個(gè)變量名的作用域總是由變量在程序中被創(chuàng)建的位置所決定的,并且與函數(shù)被調(diào)用的地點(diǎn)完全沒(méi)有關(guān)系。

  1. 如果一個(gè)變量在函數(shù)內(nèi)創(chuàng)建,它被定位在這個(gè)函數(shù)之內(nèi),那么他的作用于就是本地的。
  2. 如果一個(gè)變量在一個(gè)嵌套的函數(shù)內(nèi)創(chuàng)建,對(duì)于外層的函數(shù)來(lái)說(shuō),它是非本地的(也就是外層函數(shù)式無(wú)法訪問(wèn)的)。
  3. 如果一個(gè)變量在函數(shù)之外(也就是在Python的頂級(jí)代碼快中)創(chuàng)建,它他的作用域就是全局的。


(1) 本地作用域

在一個(gè)函數(shù)內(nèi)部對(duì)一個(gè)變量名(不包含變量成員的引用,例如:name[1]等)任何類(lèi)型的賦值(而不是在一個(gè)表達(dá)式中對(duì)其進(jìn)行引用)都會(huì)創(chuàng)建新的變量,并把新創(chuàng)建的變量劃定為本地的作用域。這包括 = 語(yǔ)句、import 語(yǔ)句、def 語(yǔ)句、函數(shù)參數(shù)名稱(chēng)等。

在默認(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)部使用的變量名不會(huì)與嵌套函數(shù)外的變量名產(chǎn)生沖突。


閉合函數(shù)

嵌套作用域的查找在嵌套的函數(shù)已經(jīng)返回后也是有效的。這種行為有時(shí)也叫作閉合(closure)函數(shù) —— 一個(gè)能夠記住嵌套作用域的變量值的函數(shù),盡管那個(gè)作用域或許已經(jīng)不存在了。

這是一種相當(dāng)高級(jí)的技術(shù),除了那些擁有函數(shù)式編程背景的程序員們,以后在實(shí)際使用中并不常見(jiàn)。另一方面,嵌套的作用域常常被 lambda 函數(shù)創(chuàng)建表達(dá)式使用——因?yàn)樗麄兪潜磉_(dá)式,它們幾乎總是嵌套在一個(gè)函數(shù)中。此外,函數(shù)嵌套通常用作裝飾器 —— 在某些情況下,它是最合理的編碼模式。

通常來(lái)說(shuō),類(lèi)是一個(gè)更好的像這樣“記憶”的選擇,因?yàn)樗鼈冏尃顟B(tài)變得很明確。不使用類(lèi)的話,全局變量、像這樣的嵌套作用域引用以及默認(rèn)的參數(shù)就是 Python 的函數(shù)能夠保留狀態(tài)信息的主要方法了。

在 Python 中作用域是可以做任意的嵌套的,但是我們應(yīng)當(dāng)盡量少的定義嵌套函數(shù)。


(2)全局作用域

函數(shù)定義了本地作用域,而模塊定義的是全局作用域。每個(gè)模塊都是一個(gè)全局作用域。

全局作用域的作用范圍僅限于單個(gè)文件。這里的全局指的是在一個(gè)文件的頂層創(chuàng)建的變量名僅對(duì)于這個(gè)文件內(nèi)部的代碼而言是全局的。在 Python 中沒(méi)有基于一個(gè)單個(gè)文件的并可以應(yīng)用在任何其他文件的全局作用域。

全局變量的一個(gè)特征是除非被刪除掉,否則它們將存活到文件運(yùn)行結(jié)束,且對(duì)于所有的函數(shù),他們的值都是可以被訪問(wèn)的。然而局部變量,就像它們存放的棧,只是在函數(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 本身并沒(méi)有預(yù)先導(dǎo)入。

上面圖片中列表中的變量名組成了 Python 中的內(nèi)置作用域。前一半是內(nèi)置的異常,后一半是內(nèi)置函數(shù)。由于下面要講的 LEGB 法則最后將自動(dòng)搜索這個(gè)模塊,將會(huì)自動(dòng)得到這個(gè)列表中的所有變量。所以你能夠使用這些變量而不需要導(dǎo)入 builtins 模塊。


2、LEGB法則

Python的變量名解析機(jī)制稱(chēng)為 LEGB 法則,這也是由作用域的命名而來(lái)的。

當(dāng)在函數(shù)中使用變量時(shí),Python搜索4個(gè) 作用域:本地作用域(L)、之后是上一層結(jié)構(gòu)中的 def 或 lambda 的本地作用域(E),之后是全局作用域(G),最后是內(nèi)置作用域(B)。并且在第一處能夠找到這個(gè)變量名的地方停下來(lái)。如果變量名在這次搜索中沒(méi)有找到,Python 會(huì)拋出 NameError 異常。


3、global 語(yǔ)句

在默認(rèn)情況下,所有在一個(gè)函數(shù)中被賦值的變量都位于這個(gè)函數(shù)的本地作用域,并且僅在這個(gè)函數(shù)運(yùn)行的過(guò)程中存在。為了在函數(shù)內(nèi)創(chuàng)建或修改一個(gè)全局作用域的變量,需要使用 global 語(yǔ)句來(lái)聲明使用全局作用域。

global 語(yǔ)句是一個(gè)使用全局命名空間的聲明,它告訴 Python 函數(shù)打算生成或修改一個(gè)或多個(gè)全局變量名。global 使得作用域查找跳過(guò)本地作用域從全局作用域開(kāi)始,如果變量不存在將繼續(xù)到內(nèi)置作用域。但是,對(duì)變量的賦值總是在全局作用域中創(chuàng)建或修改它們。

global 語(yǔ)句包含了關(guān)鍵字 global,其后跟著一個(gè)或多個(gè)由逗號(hào)分開(kāi)的變量名。當(dāng)函數(shù)主體調(diào)用時(shí),所有列出來(lái)的變量名將被映射到全局作用域內(nèi)。


最小化全局變量

在默認(rèn)情況下,函數(shù)內(nèi)部注冊(cè)的變量名是本地變量,這是有意而為之的。將其改為全局變量會(huì)引發(fā)一些軟件問(wèn)題:由于變量的值取決于函數(shù)調(diào)用的順序,而函數(shù)自身是任意順序進(jìn)行排列的,導(dǎo)致了程序調(diào)試起來(lái)變得很困難。

另一方面,不使用面向?qū)ο蟮木幊谭椒ㄒ约邦?lèi)的話,全局變量也許就是Python中最直接保持全局狀態(tài)信息的方法(函數(shù)在下次被調(diào)用時(shí)需記住的信息):本地變量在函數(shù)返回時(shí)將會(huì)消失,而全局變量不是這樣。

在不熟悉編程的情況下,最好盡可能的避免使用全局變量。


最小化文件間的修改

盡管我們能夠直接修改另一個(gè)文件中的變量,但是往往我們都不這樣做。一個(gè)模塊文件的全局變量一旦被導(dǎo)入就成為了這個(gè)模塊對(duì)象的一個(gè)屬性:導(dǎo)入者自動(dòng)得到了這個(gè)被導(dǎo)入的模塊文件的所有全局變量的訪問(wèn)權(quán)。

這會(huì)讓兩個(gè)文件有過(guò)強(qiáng)的相關(guān)性:假使它們都與變量X的值相關(guān),如果沒(méi)有其中一個(gè)文件的話很難理解或重用另一個(gè)文件。這樣隱含的跨文件依賴性,在最好的情況下會(huì)導(dǎo)致代碼不靈活,最壞的情況會(huì)引發(fā) bug。

最好的解決辦法就是別這樣做:在文件間進(jìn)行通信最好的辦法就是通過(guò)調(diào)用函數(shù),傳遞參數(shù),然后得到其返回值。


4、nonlocal 語(yǔ)句

如果需要在嵌套函數(shù)的內(nèi)層函數(shù)中直接使用外層函數(shù)中的變量,可以使用 nonlocal 語(yǔ)句來(lái)做到。

nonlocal 應(yīng)用于嵌套內(nèi)層的函數(shù)作用域中的變量名,而且在聲明 nonlocal 名稱(chēng)的時(shí)候,他聲明的變量必須已經(jīng)存在于外層嵌套函數(shù)的作用域中 。

這就允許封閉的函數(shù)作為保留狀態(tài)的一個(gè)地方——當(dāng)一個(gè)函數(shù)調(diào)用的時(shí)候,信息被記住了——而不必使用共享的全局名稱(chēng)。

nonlocal 語(yǔ)句還加快了引用——就像 global 語(yǔ)句一樣,nonlocal 使得對(duì)該語(yǔ)句中列出的名稱(chēng)的查找從嵌套的外層函數(shù)的作用域中開(kāi)始,而不是從所在函數(shù)的本地作用域開(kāi)始。也就是說(shuō),nonlocal 名稱(chēng)只能出現(xiàn)在嵌套外層函數(shù)中,作用域查找不會(huì)繼續(xù)到全局作用域或內(nèi)置作用域。

當(dāng)執(zhí)行一條 nonlocal 語(yǔ)句時(shí),nonlocal 名稱(chēng)必須已經(jīng)在一個(gè)嵌套的外層函數(shù)的作用域中創(chuàng)建,否則將會(huì)得到一個(gè)錯(cuò)誤——不能通過(guò)在嵌套的內(nèi)層函數(shù)的作用域中賦給它們一個(gè)新值來(lái)創(chuàng)建它們。


《Python基礎(chǔ)手冊(cè)》系列:

Python基礎(chǔ)手冊(cè) 1 —— Python語(yǔ)言介紹
Python基礎(chǔ)手冊(cè) 2 —— Python 環(huán)境搭建(Linux)
Python基礎(chǔ)手冊(cè) 3 —— Python解釋器
Python基礎(chǔ)手冊(cè) 4 —— 文本結(jié)構(gòu)
Python基礎(chǔ)手冊(cè) 5 —— 標(biāo)識(shí)符和關(guān)鍵字
Python基礎(chǔ)手冊(cè) 6 —— 操作符
Python基礎(chǔ)手冊(cè) 7 —— 內(nèi)建函數(shù)
Python基礎(chǔ)手冊(cè) 8 —— Python對(duì)象
Python基礎(chǔ)手冊(cè) 9 —— 數(shù)字類(lèi)型
Python基礎(chǔ)手冊(cè)10 —— 序列(字符串)
Python基礎(chǔ)手冊(cè)11 —— 序列(元組&列表)
Python基礎(chǔ)手冊(cè)12 —— 序列(類(lèi)型操作)
Python基礎(chǔ)手冊(cè)13 —— 映射(字典)
Python基礎(chǔ)手冊(cè)14 —— 集合
Python基礎(chǔ)手冊(cè)15 —— 解析
Python基礎(chǔ)手冊(cè)16 —— 文件
Python基礎(chǔ)手冊(cè)17 —— 簡(jiǎn)單語(yǔ)句
Python基礎(chǔ)手冊(cè)18 —— 復(fù)合語(yǔ)句(流程控制語(yǔ)句)
Python基礎(chǔ)手冊(cè)19 —— 迭代器
Python基礎(chǔ)手冊(cè)20 —— 生成器
Python基礎(chǔ)手冊(cè)21 —— 函數(shù)的定義
Python基礎(chǔ)手冊(cè)22 —— 函數(shù)的參數(shù)
Python基礎(chǔ)手冊(cè)23 —— 函數(shù)的調(diào)用
Python基礎(chǔ)手冊(cè)24 —— 函數(shù)中變量的作用域
Python基礎(chǔ)手冊(cè)25 —— 裝飾器
Python基礎(chǔ)手冊(cè)26 —— 錯(cuò)誤 & 異常
Python基礎(chǔ)手冊(cè)27 —— 模塊
Python基礎(chǔ)手冊(cè)28 —— 模塊的高級(jí)概念
Python基礎(chǔ)手冊(cè)29 —— 包

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1、引言 最近在刷leetcode題的時(shí)候,遇到一個(gè)求最長(zhǎng)回文子串的題目,于是,我寫(xiě)了如下的代碼: 哎呀,寫(xiě)了兩個(gè)...
    文哥的學(xué)習(xí)日記閱讀 14,525評(píng)論 6 32
  • Python作用域基礎(chǔ) 當(dāng)你在一個(gè)程序中適用變量名時(shí),Python創(chuàng)建、改變或查找變量名都是在所謂的命名空間(一個(gè)...
    聽(tīng)風(fēng)踏雪閱讀 440評(píng)論 0 0
  • Python作用域基礎(chǔ) 當(dāng)在程序中使用變量名時(shí),Python創(chuàng)建、改變或查找變量名都是在所謂的命名空間中進(jìn)行的。作...
    So_ProbuING閱讀 365評(píng)論 0 1
  • 函數(shù)和對(duì)象 1、函數(shù) 1.1 函數(shù)概述 函數(shù)對(duì)于任何一門(mén)語(yǔ)言來(lái)說(shuō)都是核心的概念。通過(guò)函數(shù)可以封裝任意多條語(yǔ)句,而且...
    道無(wú)虛閱讀 4,968評(píng)論 0 5
  • 不知道在每個(gè)人眼里,根是什么定義,我的理解是生我養(yǎng)我的地方. 不知道從什么時(shí)候開(kāi)始,我們每年回家的次數(shù)越來(lái)越少,也...
    DAJU閱讀 423評(píng)論 0 0

友情鏈接更多精彩內(nèi)容