Python為什么引入這兩個關鍵詞:global和nonlocal
本文轉載自微信公眾號「Python中文社區」,作者鞏慶奎。轉載本文請聯系Python中文社區公眾號。
啥是 global 和 nonlocal
Python 支持的關鍵詞里,global 和 nonlocal 初學者接觸的少,不知道是做什么用的;一些人雖然知道它們的作用,但對為什么要引入這兩個關鍵詞則有些不知其所以然。
粗淺地說,global 和 nonlocal 是為了在函數中修改全局和閉包變量而引入的關鍵字。
本文用代碼一點點分析引入 global 和 nonlocal 的原因。
一個奇怪的現象
下面,讓我們做一個測試。
- g =1
- def fun():
- g = 2
- return g
- print(fun(),g)
一般地,我們認為結果應該為 2,2。這一點學過其他語言如 Java、c 的同學尤其認同。
但讓我們跑起來,可以看到結果為 2,1。也就是說,函數沒有改變全局變量 g。
這很奇怪,究其原因是因為:
- Python 認為所有 = 賦值都是在當前作用域新建變量。
- 當我們在程序中 g = 1 時,表示當前全局作用域建立 g,賦值 1。
- 當我們在函數中 g = 2 時,表示當前局部作用域建立 g,賦值 2。
使用 dis.dis(fun) 分析 fun 函數源代碼:
- 16 0 LOAD_CONST 1 (2)
- 2 STORE_FAST 0 (g)
- 17 4 LOAD_FAST 0 (g)
- 6 RETURN_VALUE
可見,第 2 條指令 STORE_FAST,這是存儲到局部變量的命令。
所以,函數中實際操作的是局部變量。
還有更甚的例子如下,大家猜測下執行結果。
- g =1
- def fun():
- g += 1
- return g
- print(fun(),g)
根據上文,我們知道函數不會改變全局變量 g,那么結果應該是 2,1,這次總算對了吧?
很抱歉,當執行到 g += 1 時,系統報錯:UnboundLocalError: local variable 'g' referenced before assignment。
仔細觀察錯誤,local variable 'g',這里的 g 仍然被視為局部變量:沒有定義(=賦值),就直接 inplace add,當然要報錯。
也就是說,所有在局部作用域中對全局變量的賦值、原位賦值都會失敗。唯有如下函數給我們帶來一絲安慰。
- g =1
- def fun():
- return g
- print(fun(),g)
結果 1,1,總算還有個正常的:在局部作用域中引用全局作用域變量正常。
那當我必須修改全局變量時,該怎么辦呢?
global 的引入和分析
這就是 global 引入的理由了,將全局變量擴展到函數中來,使函數可以修改全局變量。
- g =1
- def fun():
- global g
- g = 2
- return g
- print(fun(),g)
結果為 2,2,函數修改了全局變量。我們來看 dis.dis(fun) 的反匯編代碼。
- 37 0 LOAD_CONST 1 (2)
- 2 STORE_GLOBAL 0 (g)
- 38 4 LOAD_GLOBAL 0 (g)
- 6 RETURN_VALUE
第 2 條指令,STORE_GLOBAL 是將常量 2 賦值給全局變量 g,異于上例中的 STORE_FAST指令對局部變量操作。
故此,我們得出結論:當在函數中讀取全局變量時,可以直接使用。但如果需要修改全局變量值,則需要在變量前加上 global 來修飾。
nonlocal 的引入
同樣地,當我們書寫嵌套函數,需要對閉包中的變量進行修改操作時,我們也需要引入 nonlocal 關鍵字。
如下函數中,我們定義了閉包,閉包中的變量 e,試圖在內嵌函數中進行修改,但沒有使用 nonlocal 關鍵字聲明 e。
- def outer():
- e = 1
- def inner():
- e = 2
- return e
- return inner
參照上例,我們知道這種修改是徒勞的——因為看反匯編代碼 dis.dis(outer()) 可知:
- 63 0 LOAD_CONST 1 (2)
- 2 STORE_FAST 0 (e)
- 64 4 LOAD_FAST 0 (e)
- 6 RETURN_VALUE
第 2 條指令 STORE_FAST,操作局部變量,也就是說 inner 里的 e,仍然被視為局部變量。
雷同于上例的 global,這里使用 nonlocal 來在內嵌函數 inner 中修改閉包變量 e。
- def outer():
- e = 1
- def inner():
- nonlocal e
- e = 2
- return e
- return inner
查看此時的反匯編代碼 dis.dis(outer()) 可知:
- 78 0 LOAD_CONST 1 (2)
- 2 STORE_DEREF 0 (e)
- 79 4 LOAD_DEREF 0 (e)
- 6 RETURN_VALUE
第 2 條指令 STORE_DEREF,操作的是閉包變量,也就是說 inner 里的 e,是可以修改的閉包中的 e。
總結
本文通過分析函數對全局變量和閉包變量的讀、寫操作,借助于反匯編字節碼分析,認清了 global 和 nonlocal 關鍵字的用法,對其引入和作用有了較為深刻認識。
作者:鞏慶奎,大奎,對計算機、電子信息工程感興趣。