1. 程式人生 > >注意for迴圈中變數的作用域

注意for迴圈中變數的作用域

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)