有同學曾在微信中問小編什麼是非區域性語句(nonlocal statement),本文就是對此的回答,希望沒有發的太晚。
非區域性語句是Python 3.x中新引入的特性,可以讓你給外層但非全域性作用域中的變數賦值。官方文件中的說法是,非區域性語句可以讓所列的識別符號(identifier)指向最近的巢狀作用域(enclosing scope)中已經繫結過的變數,全域性變數除外。
如果沒有非區域性語句
一般來說,巢狀函式對於其外層作用域中的變數是有訪問許可權的。
1 2 3 4 5 6 7 8 9 10 11 12 |
>>> def outside(): msg = "Outside!" def inside(): print(msg) inside() print(msg) >>> outside() Outside! Outside! |
我們在outside
函式中宣告瞭msg
變數,並賦值為“Outside!”。然後,在inside
函式中列印msg
的值。結果證明,inside
成功獲得了外層作用域中msg
的值。
但是如果我們想給外層作用域中的變數賦值時,是不是按照平常的賦值操作就可以修改它的值呢?
1 2 3 4 5 6 7 8 9 10 11 |
>>> def outside(): msg = "Outside!" def inside(): msg = "Inside!" print(msg) inside() print(msg) >>> outside() Inside! # inside函式列印的msg Outside! # outside函式列印的msg |
在inside
函式中,我們想給msg
變數賦值為”Inside!”。執行outside
時,inside
函式中msg
的值為”Inside!”,但是在outside
函式中卻保留了原先的值!
之所以出現這個情況,是因為在inside
函式中,Python實際上並沒有為之前已經建立的msg
變數賦值,而是在inside
函式的區域性作用域(local scope)中建立了一個名叫msg
的新變數,但是這樣就和外層作用域(outer scope)中的變數重名了。
這說明,巢狀函式對外層作用域中的變數其實只有只讀訪問許可權。如果我們在這個示例中的inside
函式的頂部再加一個print(msg)
語句,那麼就會出現UnboundLocalError: local variable 'msg' referenced before assignment
這個錯誤。
非區域性語句的引入,就是要儘量減少這種變數名衝突情況的出現,同時也讓巢狀函式更加方便的操作外層函式中的變數。更加詳細的原因,請看參考資料部分的PEP-3104。
使用非區域性語句之後
接下來,我們引入nonlocal語句。
1 2 3 4 5 6 7 8 9 10 11 12 |
>>> def outside(): msg = "Outside!" def inside(): nonlocal msg msg = "Inside!" print(msg) inside() print(msg) >>> outside() Inside! Inside! |
現在,我們在inside
函式的頂部新增了nonlocal msg
語句。這個語句的作用,就是告訴Python直譯器在碰到為msg
賦值的語句時,應該向外層作用域的變數賦值,而不是宣告一個重名的新變數。這樣,兩個函式的列印結果就一致了。
nonlocal
的用法和global
非常類似,只是前者針對的是外層函式作用域的變數,後者針對的則是全域性作用域的變數。
什麼時候該使用非區域性語句
有時候,你可能會疑惑什麼時候才應該使用nonlocal
。以下面的函式為例:
1 2 3 4 5 6 7 8 9 10 11 |
>>> def outside(): d = {"outside": 1} def inside(): d["inside"] = 2 print(d) inside() print(d) >>> outside() {'inside': 2, 'outside': 1} {'inside': 2, 'outside': 1} |
你可能會想,因為沒有使用nonlocal
,inside
函式中往字典d
中插入的"inside": 2
鍵值對(key-value pair)不會體現在outside
函式中。你這麼想挺合理,但卻是錯的。因為字典插入並不是賦值操作,而是方法呼叫(method call)。事實上,往字典中插入一個鍵值對相當於呼叫字典物件中的__setitem__
方法。
1 2 3 4 |
>>> d = {} >>> d.__setitem__("inside", 2) >>> d {'inside': 2} |
所以,這個示例中我們可以不使用nonlocal,就能直接操作外層作用域中的變數。
小結
其實在許多Python程式中,很少用到非區域性語句。但是,有了這種語句之後,我們就可以減少不同作用域之間變數名的衝突。非區域性語句,也讓我們更加容易地訪問、操作外層作用域中的變數。不過,這在一定程度上也讓語法變得更加複雜。
有關變數、語句等術語的基礎知識,還可以參考《Think Python 2e》的第二章:量、表示式和語句。