for e in collections: pass
在for 循環里, 最后一個對象e一直存在在上下文中。就是在循環外面,接下來對e的引用仍然有效。
這里有個問題容易被忽略,如果在循環之前已經有一個同名對象存在,這個對象是被覆蓋的。
如果在有代碼感知的IDE中, IDE會提示變量是“被重新聲明的”, 但運行時卻不會出錯。
for循環不是閉包,可以使用dis模塊分解以下代碼可以看到:
x = 5 for x in range(10): pass print x
將代碼保存到test.py文件,運行python -m dis test.py
C:\Users\Patrick\Desktop>python -m dis test.py 1 0 LOAD_CONST 0 (5) 3 STORE_NAME 0 (x) 3 6 SETUP_LOOP 20 (to 29) 9 LOAD_NAME 1 (range) 12 LOAD_CONST 1 (10) 15 CALL_FUNCTION 1 18 GET_ITER >> 19 FOR_ITER 6 (to 28) 22 STORE_NAME 0 (x) 4 25 JUMP_ABSOLUTE 19 >> 28 POP_BLOCK 6 >> 29 LOAD_NAME 0 (x) 32 PRINT_ITEM 33 PRINT_NEWLINE 34 LOAD_CONST 2 (None) 37 RETURN_VALUE
在其他語言里,for循環的初始化變量對於上下文同樣是可見的,比如java, 因為java是強類型的語言, 如果重新聲明已存在的變量IDE會提示錯誤, 當然不同通過編譯。
通常在python編程中(可能是大多數的動態語言),有時即使聲明了同名的變量,程序沒有出現明顯的錯誤,但是一旦出錯,錯誤很難被發現。所以要避免與for循環中的變量重名。
在使用python模板語言編碼時尤其如此。代碼編輯器沒有提示,不會發現錯誤在哪里。這個是我碰到的極其怪異的一個例子。為什么說怪異,因為邏輯上沒有任何問題。
在一個頁面模板里面,當handler調用這個模板時,同時傳遞了兩個對象(從handler中,我使用tornado),一個page對象和一個pages列表。我的順序是這樣的:
<!-- 用page對象 --> <label>{{ page.name if page else ''}}</label> <!-- 用pages對象 --> <label>Parent Page <select name="parent_id"> {% if pages %} {% for page in pages%} <option value="{{ page.id}}">{{page.name}}</option> {% end %} {% end %} <option value="">None</option> </select> </label> <!-- 然后又page --> <div>{{ page.markdown if page else ''}}</div>
問題來了,在運行的時候出錯了,提示在 <label>{{ page.name if page else ''}}</label> 中錯誤page referenced before assignment.
暈死了, 找了一晚上的錯,最后在把for循環中page的名字改為_page才運行了。
在模板調用過程里,模板語言也是被翻譯到python字節碼,並按行解析和出,所以根本沒有邏輯,不知道是tornado模板語言的bug。
所以注意變量名。
總之我認為tornado的exception trace非常不友好。
Python中變量的作用域搜索順序:本地作用域(Local)→當前作用域被嵌入的本地作用域(Enclosing locals)→全局/模塊作用域(Global)→內置作用域(Built-in)