Python 学习之作用域
Python 學習之作用域
文章目錄
- Python 學習之作用域
- 問題的引出
- 代碼塊
- 名稱的綁定
- 三種變量
- 作用域
- 名稱解析
- LEGB 作用域查找規則
- global 和 nonlocal
- global
- nonlocal
- 注意
- 參考資料
問題的引出
請看這樣一段代碼:
例1
# test01.py n = 5def test():n += 1print(n)test()猜猜運行結果是什么?
咱們運行一下:
$ python3 test01.py Traceback (most recent call last):File "test01.py", line 8, in <module>test()File "test01.py", line 5, in testn += 1 UnboundLocalError: local variable 'n' referenced before assignment居然出錯了,難道不應該是輸出6嗎?其實,要想弄清楚這個問題,就要系統地學習一些概念。
代碼塊
Python 程序是由代碼塊構成的。代碼塊是被作為一個單元來執行的一段 Python 程序文本。以下幾個都是代碼塊:模塊、函數體和類定義。
名稱的綁定
名稱用于指代對象(你可以理解為名稱是一個指向對象的指針)。名稱是通過名稱綁定操作來引入的。以下構造會綁定名稱:傳給函數的正式形參,import 語句,類定義,函數定義,以標識符為目標的賦值,for循環的開頭等等等等。
總之,就本文討論的問題,我們只要記住 以標識符為目標的賦值會綁定名稱 就可以了。
三種變量
如果名稱綁定在一個代碼塊中,則為該代碼塊的局部變量(通俗地說該代碼塊內定義了一個局部變量),除非聲明為 nonlocal 或 global。如果名稱綁定在模塊層級,則為全局變量。(模塊代碼塊的變量既為局部變量又為全局變量。) 如果變量在一個代碼塊中被使用但不是在其中定義,則為自由變量。
讀到這里,你可以先簡單理解為def之外定義的變量就是全局變量,def之內定義的變量就是局部變量,只讀不修改的變量就是自由變量。
再回頭看看例1的代碼,我新增加了第2行和第5行的注釋。
# test01.py n = 5 # 名稱綁定在模塊層級,n為全局變量def test():n += 1 # 因為有賦值操作,所以是名稱綁定。 名稱綁定在函數內,n為局部變量,這個n不是第2行的那個nprint(n)test()作用域
作用域定義了一個名稱在代碼塊內的可見性。
如果代碼塊中定義了一個局部變量,則其作用域包含該代碼塊,也就是說這個名稱在該代碼塊內是可見的。如果定義(名稱綁定)發生于函數代碼塊中,則其作用域會擴展到該函數所包含的任何代碼塊,除非有某個被包含代碼塊引入了對該名稱的不同綁定。
名稱綁定的位置決定了這個名稱的作用域。也就是說,一個變量的作用域總是由它在代碼中被賦值的地方所決定。
一般來說,變量可以在3個不同的地方賦值,分別對應3種不同的作用域:
注意:一個函數內部的任何類型的賦值都會把一個名稱劃定為本地的。這包括=語句,import中的模塊名稱,def中的函數名稱,函數參數名稱等。
名稱解析
所謂名稱解析,我的理解就是確定一個名稱到底指代哪個對象。舉個例子,全國有成千上萬個叫“王東”的,我說王東欠我80萬,你知道我說的是哪個王東嗎?
當一個名稱在代碼塊中被使用時,會由包含它的最近(這里的近是指地理位置近)作用域來解析。在最近作用域中,這個名稱被綁定到了哪個對象,它就指代那個對象。如果完全找不到這個名稱,將會引發NameError異常。
例2:
# test02.py n = 5 # 名稱綁定在模塊層級,n為全局變量def test():n = 6 # 名稱綁定在函數內,n為局部變量,這個n不是第2行的那個nprint(n) # 包含n的最近作用域是函數作用域,所以這個n就是第5行的ntest()這個例子的結果是輸出6。
如果一個代碼塊內的任何位置發生名稱綁定操作,則代碼塊內所有對該名稱的使用都會被認為是對當前代碼塊的引用。當一個名稱在其被綁定前就在代碼塊內被使用時則會導致錯誤。這是一個很微妙的規則。Python 缺少聲明語法,并允許名稱綁定操作發生于代碼塊內的任何位置。一個代碼塊的局部變量可通過在整個代碼塊文本中掃描名稱綁定操作來確定。
一個典型的例子是:如果名稱綁定發生于函數代碼塊中,但是該名稱被使用的時候尚未綁定到特定值,那么將會引發UnboundLocalError異常。UnboundLocalError 為 NameError 的一個子類。
講到這里,你應該明白例1為什么會報錯了吧。
請注意第5行的注釋。
# test01.py n = 5def test():n += 1 # 這句確實是名稱綁定,但是先要讀出n的值,這時候n尚未綁定到特定值。print(n)test()咱們再看一個例子。
例3
請思考一下,結果會是什么。
結果是:
$ python3 test03.py Traceback (most recent call last):File "test03.py", line 8, in <module>test()File "test03.py", line 5, in testprint(n) UnboundLocalError: local variable 'n' referenced before assignment同例1一樣,發生了 UnboundLocalError
我們分析一下,請看注釋:
# test03.py n = 5 # 名稱綁定在模塊層級,n為全局變量def test():print(n)# 如果一個代碼塊內的任何位置發生名稱綁定操作,# 則代碼塊內所有對該名稱的使用都會被認為是對當前代碼塊的引用,# 所以這里的n就是第10行的n,n的使用在第10行的綁定之前,所以會導致錯誤n = 7 # 發生了名稱綁定,代碼塊內所有對該名稱的使用都會被認為是這個ntest()LEGB 作用域查找規則
前文已經說過:當一個名稱在代碼塊中被使用時,會由包含它的最近作用域來解析。具體來說,當引用一個變量時,Python 按照以下順序查找:
Python 會在第一處能找到這個變量名的地方停下來。
global 和 nonlocal
global 語句用來表明特定變量生存于全局作用域并且應當在其中被重新綁定;nonlocal 語句表明特定變量生存于外層作用域并且應當在其中被重新綁定。
也許你不理解這句話,沒有關系,下文會舉例說明。
global
如果需要給一個在函數內部卻屬于模塊頂層的變量名賦值,需要在函數內部用 global 語句聲明。
例4
# test04.py n = 5def test():global n # 聲明函數內的n是第2行的全局變量nn += 1 # 全局變量n加1print(n) # 打印全局變量test() # 輸出 6 print(n) # 輸出 6,說明全局變量確實被修改了例5
# test05.py n = 5 # 名稱綁定在模塊層級,n為全局變量def test():print(n) # 引用第2行的全局變量,可以不聲明test() # 輸出5要點
全局變量如果在函數內被賦值的話,必須經過聲明。
全局變量如果是在函數內被引用(只是讀),可以不聲明。
nonlocal
如果需要給一個在嵌套的def中卻屬于外層函數的變量名賦值,需要在嵌套的def中用 nonlocal 聲明(從Python 3.0 開始)。
如果沒有聲明,這些變量將是只讀的(嘗試寫入這樣的變量只會在最內層作用域中創建一個新的局部變量,而同名的外部變量保持不變)。
例6
# test06.py x = 99 # 名稱綁定在模塊層級,x為全局變量def f1():x = 88 # 名稱綁定在函數f1內,x為函數f1的局部變量def f2():print(x) # 根據LEGB規則,引用第5行的xf2()f1() # 輸出88例7
# test07.py def func():x = 66 # 名稱綁定在函數func內,x為函數func的局部變量def nested():nonlocal x # 聲明x是外層函數中的x,即第3行定義的xx = 88 # 修改第3行的x,而不是重新定義一個xnested()print(x)func() # 輸出88如果去掉第5行:
例8
# test08.py def func():x = 66 # 名稱綁定在函數func內,x為函數func的局部變量def nested():x = 88 # 重新定義一個x,不同于第3行的xnested()print(x) # 引用第3行的xfunc() # 輸出66注意
例9
i = 10 def f():print(i) # i是自由變量,在運行時解析。雖然它就是指第1行的i,但是在運行時,i的值是42 i = 42 f() # 打印42例10
# test10.py lst = [1,2,3]def test():print(lst) # 引用第2行的lstlst[0] += 10 # 原處改變對象,而不是對變量名lst賦值,此處的lst是引用第2行的lstprint(lst)test() print(lst)運行結果是:
$ python3 test10.py [1, 2, 3] [11, 2, 3] [11, 2, 3]【End】
參考資料
《Learning Python,5th Edition》
《Python Tutorial,Release 3.7.2》
《The Python Language Reference, Release 3.7.2》
總結
以上是生活随笔為你收集整理的Python 学习之作用域的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: eclipse一套全部流程的安装及配置
- 下一篇: Python学习之变量、对象和引用