python的变量作用域
1. 不在函數體內的變量或者在 if __name__=='__main__'中的變量,都是全局變量,注意訪問這些全局變量的速度是比較慢的,因為這些全局變量放在一個全局的表中,需要查找
2. 在函數體內,如果不沒有對變量的賦值操作,默認這個變量是全局變量,就是要從全局的表中查找
3. 在函數體內,如果有對變量的賦值操作,則這邊變量是局部變量;如果想在函數體內修改全局變量,只需在函數體內聲明global即可
http://hyry.dip.jp/tech/book/page/python/variable_scope_global.html
訪問全局變量
在函數內部可以訪問全局變量,例如:
def f1():print x在函數f1()創建時,無論表示全局對象字典的func_globals屬性中是否存在”x”,在函數內部變量x都會當作全局變量處理。只要在f1()運行時能找到全局變量x即可。通過代碼對象的co_names屬性可以獲得代碼中訪問的全局變量名,而co_varnames屬性可以獲得代碼中使用的局域變量名。
co_names并不是單純用來保存全局變量名,后面我們還會看到其它的用法。 >>> run variable_scope02.py >>> f1.func_code.co_names # 全局變量名 ('x',) >>> f1.func_code.co_varnames # 局域變量名 ()如果函數中存在對變量x賦值的語句,那么它將被當作函數的局域變量:
def f2():x = 10print x >>> f2.func_code.co_names () >>> f2.func_code.co_varnames ('x',)通過查看字節碼,可以發現其中的區別:
>>> dis.dis(f1) 4 0 LOAD_GLOBAL 0 (x) 3 PRINT_ITEM 4 PRINT_NEWLINE 5 LOAD_CONST 0 (None) 8 RETURN_VALUE>>> dis.dis(f2) 10 0 LOAD_CONST 1 (10) 3 STORE_FAST 0 (x) 11 6 LOAD_FAST 0 (x) 9 PRINT_ITEM 10 PRINT_NEWLINE 11 LOAD_CONST 0 (None) 14 RETURN_VALUE在f1()中使用LOAD_GLOBAL命令載入變量x的值,而在f2()中則使用LOAD_FAST和STORE_FAST命令存取變量的值。查看這幾個命令的幫助可知:
- LOAD_GLOBAL在代碼對象的co_names屬性中尋找變量名。
- LOAD_FAST和STORE_FAST在代碼對象的co_varnames屬性中尋找變量名。
請注意這些命令的參數都為整數,變量名是通過將參數作為下標從對應的變量名元組中獲得的。所以:“LOAD_GLOBAL 0”相當于載入f1.func_globals[f1.func_code.co_names[0]]對象。
對于局域變量,Python做了盡可能的優化處理,因此它使用LOAD_FAST和STORE_FAST對局域變量進行存取,它們可以通過其參數直接存取一個用來保存局域變量的C語言數組。如果我們希望函數修改全局變量,那么需要在函數內使用global關鍵字進行聲明。
def f3():global xx = 20print x >>> dis.dis(f3) 17 0 LOAD_CONST 1 (20) 3 STORE_GLOBAL 0 (x) 18 6 LOAD_GLOBAL 0 (x) 9 PRINT_ITEM 10 PRINT_NEWLINE 11 LOAD_CONST 0 (None) 14 RETURN_VALUE可以看到當使用global關鍵字將變量x聲明為全局變量之后,字節碼中就改為使用STORE_GLOBAL和LOAD_GLOBAL命令了。
由上述的結果我們可以做如下總結:
- 在函數內未被賦值而直接使用的變量為全局變量。
- 在函數內用global聲明的均為全局變量,否則若存在賦值語句,則被賦值的變量為局域變量。
全局變量不簡單
全局變量的規則雖然簡單,但是稍有不慎就會出現一些意想不到的錯誤。為了防范于未然,讀者要牢記Python是先編譯成字節碼之后再運行的,在編譯時變量是全局的還是局域的就已經決定了。然而編譯器的工作方式和我們通常理解程序的方式存在區別,因此會出現一些難以發覺的錯誤。
請避免編寫如下容易引起理解混淆的程序。 x = 0 def f1(flag):if flag:global xx = 10x = 20print xf1(False) print x上面的程序的輸出如下:
20 20這是因為無論global關鍵字在何處,它的聲明對整個函數體都是有效的。當編譯器發現函數體中存在“global x”語句,它就把變量x當作全局變量處理。這和我們對代碼的直觀理解不同,因此請避免編寫這種代碼。
下面再看一個錯誤更加隱蔽的例子:
def f2(x):print len(x) ?if False:len = 10 ?print len try:f2([1,2,3]) except Exception as ex:print ex由于f2()會拋出異常,因此我們在調用它時用try/except捕捉了錯誤。程序的輸出為:
local variable 'len' referenced before assignment和global關鍵字一樣,無論對變量的賦值出現在何處,這個變量都會被當作局域變量。甚至它出現在明顯不可能被執行的位置。在f2()中,?由于存在語句“len = 10”,因此len被當作局域變量,在字節碼中會使用LOAD_FAST命令載入變量len。?而運行“len(x)”載入變量len所引用的對象時,LOAD_FAST在局域變量字典中找不到,于是報錯。
雖然?是真正需要修改的語句,Python卻會在?處報錯。特別是當函數體較長時,這個錯誤很難發覺。因此使用內置函數或模塊名作為變量名是需要絕對避免的,當你需要用len做為變量名時,可以考慮多添加一個下劃線:len_。
exec語句的影響
通過上節的敘述我們知道Python編譯器通過global關鍵字和變量賦值語句決定某個變量是全局還是局域的。然而由于Python是如此的動態,我們可以在函數內部用exec命令動態地將字符串當作程序執行。對于Python編譯器來說,這個表示程序的字符串是無法在程序的編譯期進行分析的。因此Python必須對其進行額外的處理。
先看一個最簡單的例子:
a = 0 def f1():exec("a=100")print a f1() print a程序的輸出為:
100 0顯然Python將變量a當作局域變量處理。但是它應該不能解析exec語句到底執行了什么程序,從而判斷變量a是一個局域變量。讓我們查看一下編譯之后的字節碼:
>>> dis.dis(f1) 7 0 LOAD_CONST 1 ('a=100') 3 LOAD_CONST 0 (None) 6 DUP_TOP 7 EXEC_STMT 8 8 LOAD_NAME 0 (a) <--- 使用LOAD_NAME載入變量a的值 11 PRINT_ITEM 12 PRINT_NEWLINE 13 LOAD_CONST 0 (None) 16 RETURN_VALUE可以看到字節碼中使用LOAD_NAME命令載入變量a的值,而不是LOAD_GLOBAL或LOAD_FAST。LOAD_NAME命令從代碼對象的co_names屬性讀取變量名,然后依次從局域變量的字典以及全局變量的字典尋找對應的值。
>>> f1.func_code.co_names ('a',)由于exec命令在局域變量字典中創建了變量a,因此LOAD_NAME先找到了它。
和前面所介紹的global關鍵字以及賦值語句一樣,只要函數體中出現exec語句,那么被之前判斷為全局的變量都采用LOAD_NAME命令載入。這樣,當exec語句動態地創建了局域變量時,能優先載入局域變量的值,當局域變量不存在時,再載入全局變量。
在函數中被賦值的變量,仍然會被當作局域變量,而被global聲明的變量則仍然被當作全局變量。
def f3():exec("")a = 1global bprint a,b,c因此對于上面的程序,變量a使用LOAD_FAST載入,變量b使用LOAD_GLOBAL載入,而變量c使用LOAD_NAME載入。請讀者自行通過查看字節碼驗證。
請讀者思考下面的程序的輸出,并通過字節碼進行說明。 a = 0 def f2():print aexec("a=10")print a f2() print a由于多了一次查找,因此LOAD_NAME比LOAD_GLOBAL的執行效率略低。下面的程序比較二者的區別。
variable_scope_exec_time.py
當在函數內存在exec語句時,會影響全局變量的查找速度
import time a = 1 def f1():exec ""start = time.clock()sum_ = 0for i in xrange(100000):sum_ += aprint "with exec:", time.clock() - startdef f2():start = time.clock()sum_ = 0for i in xrange(100000):sum_ += aprint "without exec:", time.clock() - startf1() f2()程序的輸出為:
with exec: 0.0217457921526 without exec: 0.0197440029085
比較全局變量和局部變量的速度
http://coolshell.cn/articles/7886.html
考慮下面的代碼,一個在函數體內,一個是全局的代碼。
函數內的代碼執行效率為 1.8s
| 1 2 3 4 | def main(): ????for i in xrange(10**8): ????????pass main() |
函數體外的代碼執行效率為 4.5s
| 1 2 | for i in xrange(10**8): ????pass |
不用太糾結時間,只是一個示例,我們可以看到效率查得很多。為什么會這樣呢?我們使用?dis?module?反匯編函數體內的bytecode 代碼,使用?compile?builtin?反匯編全局bytecode,我們可以看到下面的反匯編(注意我高亮的地方)
| 1 2 3 | 13 FOR_ITER ? ? ? ? ? ? ? ? 6 (to 22) 16 STORE_FAST ? ? ? ? ? ? ? 1 (i) 19 JUMP_ABSOLUTE ? ? ? ? ? 13 |
| 1 2 3 | 13 FOR_ITER ? ? ? ? ? ? ? ? 6 (to 22) 16 STORE_NAME ? ? ? ? ? ? ? 1 (i) 19 JUMP_ABSOLUTE ? ? ? ? ? 13 |
我們可以看到,差別就是?STORE_FAST?和?STORE_NAME,前者比后者快很多。所以,在全局代碼中,變量i成了一個全局變量,而函數中的i是放在本地變量表中,所以在全局變量表中查找變量就慢很多。如果你在main函數中聲明global i 那么效率也就下來了。原因是,本地變量是存在一個數組中(直到),用一個整型常量去訪問,而全局變量存在一個dictionary中,查詢很慢。
總結
以上是生活随笔為你收集整理的python的变量作用域的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: replaceAll的坑
- 下一篇: 集合与数组的转换