摘要
- global?標志實際上是為了提示 python 解釋器,表明被其修飾的變量是全局變量。這樣解釋器就可以從當前空間 (current scope) 中讀寫相應變量了。
- Python 的全局變量是模塊 (module) 級別的
- 每個 python 函數(shù)擁有對應的?__globals__?字典,該字典與函數(shù)所屬模塊的?__dict__?字典完全相同。函數(shù)的全局變量也會從這個字典中獲取
- 注:上面三句話的意思就是,python 解釋器發(fā)現(xiàn)函數(shù)中的某個變量被?global?關(guān)鍵字修飾,就去函數(shù)的?__globals__?字典變量中尋找(因為 python 中函數(shù)也是一等對象);同時,一個模塊中每個函數(shù)的?__globals__?字典變量都是模塊?__dict__?字典變量的引用,二者值完全相同。
- 避免全局變量將使得程序更容易被調(diào)試,同時也能提升程序的可讀性
- 使用到的全局變量只是作為引用,不在函數(shù)中修改它的值的話,不需要加global關(guān)鍵字.?使用到的全局變量,需要在函數(shù)中修改的話,就涉及到歧義問題. 因此在函數(shù)中修改全局變量的話需要加global關(guān)鍵字
動機
我最近遇到了一個關(guān)于 python 全局變量的問題,如下面這個簡單例子里展示(當然實際代碼要比這個復雜的多,這里只是一個抽象出來當例子)。例子中?foo.py?定義了函數(shù)?f,而函數(shù)?f?調(diào)用了全局變量?a:
# foo.py def f(): print(a) def main(): global a a = 5 f() if __name__ == '__main__': main()
運行上面這個文件將如預料中的輸出5。在另一個文件?bar.py?中我們引入上面的?f,代碼如下
# bar.py from foo import f def main(): f() main()
運行?bar.py?將報?NameError?錯誤。這是因為?a?被定義在?foo.py?的?main?函數(shù)中,而當導入?f?函數(shù)時,?foo.py?的?main?函數(shù)并未被運行,所以?a?也沒喲被定義。
Traceback (most recent call last): File "bar.py", line 10, inmain() File "bar.py", line 7, in main f() File "foo.py", line 5, in f print(a) NameError: global name 'a' is not defined
定義全局變量?a
為了修復上面當問題第一反應是在?bar.py?中定義全局變量?a,這樣?f?就可以找到變量?a?了,如下面的代碼:
# bar.py from foo import f def main(): global a a = 4 f() main()
然而依舊會報錯,黑人問號臉???
Traceback (most recent call last): File "/tmp/example/bar.py", line 13, inmain() File "/tmp/example/bar.py", line 9, in main f() File "/tmp/example/foo.py", line 5, in f print(a) NameError: global name 'a' is not defined
函數(shù)的?__globals__?屬性與 python 的?global?語句
python 的?global?語句的作用只是提示 python 解釋器,被?global?修飾的變量是一個全局變量,利用上面例子里函數(shù)?f?的反編譯代碼可以清除的看到這一點:
import dis from foo import f dis.dis(f)
5 0 LOAD_GLOBAL 0 (print) 2 LOAD_GLOBAL 1 (a) 4 CALL_FUNCTION 1 6 POP_TOP 8 LOAD_CONST 0 (None) 10 RETURN_VALUE
面可以看出變量?a?被認為是全局變量。Python 中的每一個函數(shù)都擁有一個?__globals__?字典變量,該變量實際是函數(shù)所屬模塊的?__dict__?變量的引用。所以在?bar.py?中我們想在?bar.main?函數(shù)中將全局變量?a?賦值為4,實際改變的是?bar.py?的?__dict__?字典變量 (注:而不是定義?f?的?foo.py?的?__dict__?字典變量)
# bar.py def main(): global a a = 4 print(main.__globals__.keys()) print(main.__globals__['a']) dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__annotations__', '__builtins__', '__file__', '__cached__', 'foo', 'f', 'dis', 'main', 'a']) 4
上面的代碼輸出了?main.__globals__?(即?bar.__dict__?) 中全局變量?a?的值是4,然而這個值對函數(shù)?f?來說確是不可見的,因為?f.__globals__?實際等于?foo.__dict__?(簡單而言就是命名空間不同)
from foo import f print(f.__globals__)
假設(shè)我們在?foo.py?所有函數(shù)的外部預先定義了全局變量?a?,那么在將函數(shù)?f?導入時,a?會隨著?f.__globals__?一同被導入。但這時被導入的?f.__globals__["a"]?( 即?foo.__dict__["a"]?) 和?bar.main?中賦值的?bar.main.__globals__["a"]?( 即?bar.__dict__["a"]?) 仍然不是同一個變量,即賦值無法改變函數(shù)?f?的輸出,如下面的例子所示。
# foo.py a = 3 def f(): print(a) def main(): global a a = 5 f() if __name__ == '__main__': main()
# bar.py from foo import f def main(): global a a = 4 f() main()
運行?bar.py?輸出3,而不是 4。
修改函數(shù)全局變量的值:更新?globals
就上述例子而言,如果我們想在?bar.py?中改變函數(shù)?f?的輸出,則需要直接更新其?__globals__?變量的值。
# bar.py from foo import f def main(): f.__globals__['a'] = 4 f() main()
-
模塊的?dict?變量和猴子布丁 (monkey-patching)
如上所述,函數(shù)的?__globals__?變量實際是其所屬模塊?__dict__?變量的引用。所以為了達到上面修改全局變量的目的,也可以直接更新?foo.__dict__?。修改模塊?foo?的屬性 (attribute) 值即可直接更新?foo.__dict__?。
# bar.py import foo from foo import f def main(): foo.a = 4 f()
如果你曾經(jīng)使用過運行中給代碼打補丁的庫,一般就是這么實現(xiàn)的。直接修改被打補丁的模塊的?__dict__?中特定的對象或函數(shù)。、
輸入使得函數(shù)變得更加容易測試
上面的例子中的函數(shù)?f?如果接受輸入變量的話,而不是使用全局變量,代碼將更容易被測試。同時可讀性也更好,出了問題也更容易 debug。
# foo.py def f(a): print(a) def main(): a = 5 f(a) if __name__ == '__main__': main()
# bar.py from foo import f def main(): a = 3 f(a)
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
更多文章、技術(shù)交流、商務合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
