Python基礎手冊24——函數中變量的作用域

五、變量的作用域

當你在一個程序中使用變量名時,Python創建、改變或查找變量名都是在命名空間(一個保存變量名的地方,這個地方的范圍也叫作變量的作用域)中進行的。

在創建變量時,Python將變量名被創建的地點關聯給(綁定給)一個特定的命名空間。也就是說在代碼中變量創建的位置決定了這個變量將存在于哪個命名空間,也就是它可以被訪問的范圍。

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


1、作用域

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

在任何情況下,一個變量名的作用域總是由變量在程序中被創建的位置所決定的,并且與函數被調用的地點完全沒有關系。

  1. 如果一個變量在函數內創建,它被定位在這個函數之內,那么他的作用于就是本地的。
  2. 如果一個變量在一個嵌套的函數內創建,對于外層的函數來說,它是非本地的(也就是外層函數式無法訪問的)。
  3. 如果一個變量在函數之外(也就是在Python的頂級代碼快中)創建,它他的作用域就是全局的。


(1) 本地作用域

在一個函數內部對一個變量名(不包含變量成員的引用,例如:name[1]等)任何類型的賦值(而不是在一個表達式中對其進行引用)都會創建新的變量,并把新創建的變量劃定為本地的作用域。這包括 = 語句、import 語句、def 語句、函數參數名稱等。

在默認的情況下,函數內創建的所有變量都是與函數的本地命名空間相關聯的。這意味著:一個在 def 內定義的變量能夠被 def 內的代碼使用,不能在函數的外部被引用。

當在函數之外給一個變量名賦值時(也就是,在一個模塊文件的頂層,或者是在交互提示模式下),本地作用域與全局作用域(這個模塊的命名空間)是相同的。

本地變量作為臨時的變量名,只有在函數運行時才需要他們。


嵌套作用域

Python 為嵌套函數提供了嵌套的命名空間(作用域),使得嵌套在內部的函數內創建的變量名本地化,以便嵌套函數內部使用的變量名不會與嵌套函數外的變量名產生沖突。


閉合函數

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

這是一種相當高級的技術,除了那些擁有函數式編程背景的程序員們,以后在實際使用中并不常見。另一方面,嵌套的作用域常常被 lambda 函數創建表達式使用——因為他們是表達式,它們幾乎總是嵌套在一個函數中。此外,函數嵌套通常用作裝飾器 —— 在某些情況下,它是最合理的編碼模式。

通常來說,類是一個更好的像這樣“記憶”的選擇,因為它們讓狀態變得很明確。不使用類的話,全局變量、像這樣的嵌套作用域引用以及默認的參數就是 Python 的函數能夠保留狀態信息的主要方法了。

在 Python 中作用域是可以做任意的嵌套的,但是我們應當盡量少的定義嵌套函數。


(2)全局作用域

函數定義了本地作用域,而模塊定義的是全局作用域。每個模塊都是一個全局作用域。

全局作用域的作用范圍僅限于單個文件。這里的全局指的是在一個文件的頂層創建的變量名僅對于這個文件內部的代碼而言是全局的。在 Python 中沒有基于一個單個文件的并可以應用在任何其他文件的全局作用域。

全局變量的一個特征是除非被刪除掉,否則它們將存活到文件運行結束,且對于所有的函數,他們的值都是可以被訪問的。然而局部變量,就像它們存放的棧,只是在函數調用時暫時地存在,僅僅只依賴于定義它們的函數現階段是否處于活動狀態。


(3)內置作用域

內置作用域僅僅是一個名為 builtins 的內置模塊,必須要 import builtins 之后才能使用這個模塊,因為變量名 builtins 本身并沒有預先導入。

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


2、LEGB法則

Python的變量名解析機制稱為 LEGB 法則,這也是由作用域的命名而來的。

當在函數中使用變量時,Python搜索4個 作用域:本地作用域(L)、之后是上一層結構中的 def 或 lambda 的本地作用域(E),之后是全局作用域(G),最后是內置作用域(B)。并且在第一處能夠找到這個變量名的地方停下來。如果變量名在這次搜索中沒有找到,Python 會拋出 NameError 異常。


3、global 語句

在默認情況下,所有在一個函數中被賦值的變量都位于這個函數的本地作用域,并且僅在這個函數運行的過程中存在。為了在函數內創建或修改一個全局作用域的變量,需要使用 global 語句來聲明使用全局作用域。

global 語句是一個使用全局命名空間的聲明,它告訴 Python 函數打算生成或修改一個或多個全局變量名。global 使得作用域查找跳過本地作用域從全局作用域開始,如果變量不存在將繼續到內置作用域。但是,對變量的賦值總是在全局作用域中創建或修改它們。

global 語句包含了關鍵字 global,其后跟著一個或多個由逗號分開的變量名。當函數主體調用時,所有列出來的變量名將被映射到全局作用域內。


最小化全局變量

在默認情況下,函數內部注冊的變量名是本地變量,這是有意而為之的。將其改為全局變量會引發一些軟件問題:由于變量的值取決于函數調用的順序,而函數自身是任意順序進行排列的,導致了程序調試起來變得很困難。

另一方面,不使用面向對象的編程方法以及類的話,全局變量也許就是Python中最直接保持全局狀態信息的方法(函數在下次被調用時需記住的信息):本地變量在函數返回時將會消失,而全局變量不是這樣。

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


最小化文件間的修改

盡管我們能夠直接修改另一個文件中的變量,但是往往我們都不這樣做。一個模塊文件的全局變量一旦被導入就成為了這個模塊對象的一個屬性:導入者自動得到了這個被導入的模塊文件的所有全局變量的訪問權。

這會讓兩個文件有過強的相關性:假使它們都與變量X的值相關,如果沒有其中一個文件的話很難理解或重用另一個文件。這樣隱含的跨文件依賴性,在最好的情況下會導致代碼不靈活,最壞的情況會引發 bug。

最好的解決辦法就是別這樣做:在文件間進行通信最好的辦法就是通過調用函數,傳遞參數,然后得到其返回值。


4、nonlocal 語句

如果需要在嵌套函數的內層函數中直接使用外層函數中的變量,可以使用 nonlocal 語句來做到。

nonlocal 應用于嵌套內層的函數作用域中的變量名,而且在聲明 nonlocal 名稱的時候,他聲明的變量必須已經存在于外層嵌套函數的作用域中 。

這就允許封閉的函數作為保留狀態的一個地方——當一個函數調用的時候,信息被記住了——而不必使用共享的全局名稱。

nonlocal 語句還加快了引用——就像 global 語句一樣,nonlocal 使得對該語句中列出的名稱的查找從嵌套的外層函數的作用域中開始,而不是從所在函數的本地作用域開始。也就是說,nonlocal 名稱只能出現在嵌套外層函數中,作用域查找不會繼續到全局作用域或內置作用域。

當執行一條 nonlocal 語句時,nonlocal 名稱必須已經在一個嵌套的外層函數的作用域中創建,否則將會得到一個錯誤——不能通過在嵌套的內層函數的作用域中賦給它們一個新值來創建它們。


《Python基礎手冊》系列:

Python基礎手冊 1 —— Python語言介紹
Python基礎手冊 2 —— Python 環境搭建(Linux)
Python基礎手冊 3 —— Python解釋器
Python基礎手冊 4 —— 文本結構
Python基礎手冊 5 —— 標識符和關鍵字
Python基礎手冊 6 —— 操作符
Python基礎手冊 7 —— 內建函數
Python基礎手冊 8 —— Python對象
Python基礎手冊 9 —— 數字類型
Python基礎手冊10 —— 序列(字符串)
Python基礎手冊11 —— 序列(元組&列表)
Python基礎手冊12 —— 序列(類型操作)
Python基礎手冊13 —— 映射(字典)
Python基礎手冊14 —— 集合
Python基礎手冊15 —— 解析
Python基礎手冊16 —— 文件
Python基礎手冊17 —— 簡單語句
Python基礎手冊18 —— 復合語句(流程控制語句)
Python基礎手冊19 —— 迭代器
Python基礎手冊20 —— 生成器
Python基礎手冊21 —— 函數的定義
Python基礎手冊22 —— 函數的參數
Python基礎手冊23 —— 函數的調用
Python基礎手冊24 —— 函數中變量的作用域
Python基礎手冊25 —— 裝飾器
Python基礎手冊26 —— 錯誤 & 異常
Python基礎手冊27 —— 模塊
Python基礎手冊28 —— 模塊的高級概念
Python基礎手冊29 —— 包

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內容

  • 1、引言 最近在刷leetcode題的時候,遇到一個求最長回文子串的題目,于是,我寫了如下的代碼: 哎呀,寫了兩個...
    文哥的學習日記閱讀 14,356評論 6 32
  • Python作用域基礎 當你在一個程序中適用變量名時,Python創建、改變或查找變量名都是在所謂的命名空間(一個...
    聽風踏雪閱讀 359評論 0 0
  • Python作用域基礎 當在程序中使用變量名時,Python創建、改變或查找變量名都是在所謂的命名空間中進行的。作...
    So_ProbuING閱讀 311評論 0 1
  • 函數和對象 1、函數 1.1 函數概述 函數對于任何一門語言來說都是核心的概念。通過函數可以封裝任意多條語句,而且...
    道無虛閱讀 4,611評論 0 5
  • 不知道在每個人眼里,根是什么定義,我的理解是生我養我的地方. 不知道從什么時候開始,我們每年回家的次數越來越少,也...
    DAJU閱讀 401評論 0 0