五、變量的作用域
當你在一個程序中使用變量名時,Python創建、改變或查找變量名都是在命名空間(一個保存變量名的地方,這個地方的范圍也叫作變量的作用域)中進行的。
在創建變量時,Python將變量名被創建的地點關聯給(綁定給)一個特定的命名空間。也就是說在代碼中變量創建的位置決定了這個變量將存在于哪個命名空間,也就是它可以被訪問的范圍。
函數的作用域有助于防止程序之中變量名的沖突,并且有助于函數成為更加獨立的程序單元。
1、作用域
變量的作用域可以分為:本地作用域、全局作用域和內置作用域。
在任何情況下,一個變量名的作用域總是由變量在程序中被創建的位置所決定的,并且與函數被調用的地點完全沒有關系。
- 如果一個變量在函數內創建,它被定位在這個函數之內,那么他的作用于就是本地的。
- 如果一個變量在一個嵌套的函數內創建,對于外層的函數來說,它是非本地的(也就是外層函數式無法訪問的)。
- 如果一個變量在函數之外(也就是在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 —— 包