2016-11-26 29 views
14
a = 10 
def f(): 
    print(1) 
    print(a) # UnboundLocalError raised here 
    a = 20 
f() 

這段代碼當然會產生UnboundLocalError: local variable 'a' referenced before assignment。但爲什麼在print(a)產品線上提出這個例外?爲什麼分配給全局變量的錯誤提前引發異常?

如果解釋器一行一行地執行代碼(就像我認爲的那樣),當達到print(a)時它不知道什麼是錯的;它會認爲a指的是全局變量。

因此,解釋器提前讀取整個函數來確定是否使用a進行賦值。這是記錄在任何地方?除了檢查語法錯誤外,是否還有其他任何解釋器向前看的場合?要明確,異常本身是非常清楚的:全局變量可以在沒有global聲明的情況下讀取,但不能寫入(這種設計可以防止由於無意修改全局變量而導致的錯誤;這些錯誤尤其難以調試,因爲它們會導致錯誤遠離錯誤代碼的位置)。我只是好奇爲什麼這個例外很早就提出來了。

+1

我想你會發現默認情況下python無法從函數內部訪問全局變量。你將不得不明確聲明你的意思是使用全局。 (btw不要使用全局變量)。 – quamrana

+6

@quamrana不真實。如果您將作業移除到本地'a',代碼將打印'10'。 – 2rs2ts

+0

對於它的價值,這不是特定於python 3,只是在python 2中測試,併發生相同的事情。 – 2rs2ts

回答

14

Python's documentation,解釋器首先會注意到的在f()(無論在功能分配的位置)的範圍名爲a變量賦值,然後作爲結果只能識別變量a爲本地在此範圍內變量。此行爲有效shadows全局變量a

這個異常然後會在「提前」引發,因爲執行代碼「逐行」的解釋器會遇到引用局部變量的print語句,這個變量在這一點上還沒有綁定(請記住,Python正在尋找在這裏變量爲local)。

正如你在你的問題中提到,一個必須使用global關鍵字明確地告訴大家,在此範圍內的分配做了全局變量,編譯器正確的代碼是:

a = 10 
def f(): 
    global a 
    print(1) 
    print(a) # Prints 10 as expected 
    a = 20 
f() 

由於@2rs2ts在一個現在被刪除的答案中說,這很容易解釋爲「Python不僅僅被解釋,它被編譯成字節碼,而不僅僅是逐行解釋」。

+0

腳本編譯爲字節碼而不是逐行解釋的事實是否有其他(稍微)意外的後果? – max

+0

不是我意識到我的頭頂,但我可能是錯的。我知道的一點是,Python並沒有被編譯器優化太多,因爲語言是非常動態的(沒有靜態類型,方法可以在運行中被替換等),所以它非常困難。一般而言,代碼僅編譯爲字節碼,以便在每次函數調用時刪除解析時間(運行時只是在大查找表中查找字節碼,而不是再次解析所有內容)。 cc @max –

+1

@max:彙編和解釋與你的問題完全和完全無關。您在詢問*語義*,即Python語言規範。 Python語言規範說,除非聲明爲'global',否則在函數中分配的所有變量都是本地的。期。編譯和解釋是* Pragmatics *的問題,即任何特定的Python實現。但是任何特定的Python實現,無論它是編譯還是解釋,都必須遵守Python語言規範。否則它不會*是一個「Python實現」,它... –

8

在Python參考手冊此陳述的Resolution of Names部分:

[..]如果當前範圍是一個函數範圍和名稱是指一個局部變量還沒有被勢必會在其中所使用的名稱的點值,一個UnboundLocalError引發異常[..]

這時候UnboundLocalError發生在官方消息。如果您在CPython的字節碼與dis生成的功能f看看你可以看到它試圖從局部範圍加載名稱時,其價值甚至還沒有尚未設置:

>>> dis.dis(f) 
    3   0 LOAD_GLOBAL    0 (print) 
       3 LOAD_CONST    1 (1) 
       6 CALL_FUNCTION   1 (1 positional, 0 keyword pair) 
       9 POP_TOP 

    4   10 LOAD_GLOBAL    0 (print) 
      13 LOAD_FAST    0 (a)  # <-- this command 
      16 CALL_FUNCTION   1 (1 positional, 0 keyword pair) 
      19 POP_TOP 

    5   20 LOAD_CONST    2 (20) 
      23 STORE_FAST    0 (a) 
      26 LOAD_CONST    0 (None) 
      29 RETURN_VALUE 

,你可以看到名字'a'LOAD_FAST命令的方式加載到堆棧:

   13 LOAD_FAST    0 (a) 

這是用來搶在功能本地變量的命令(名爲FAST,由於它是相當快比從全球範圍加載LOAD_GLOBAL)。

這與以前定義的全局名稱a無關。這與CPython會假設你打得很好並且生成LOAD_FAST以參考'a'這一事實有關,因爲'a'正在中被分配給(即作爲本地名稱)在函數體內部。

對於一個名稱進入,並沒有相應的分配功能,CPython中不產生LOAD_FAST,而是與LOAD_GLOBAL去,並着眼於全球範圍內:

>>> def g(): 
... print(b) 
>>> dis.dis(g) 
    2   0 LOAD_GLOBAL    0 (print) 
       3 LOAD_GLOBAL    1 (b) 
       6 CALL_FUNCTION   1 (1 positional, 0 keyword pair) 
       9 POP_TOP 
      10 LOAD_CONST    0 (None) 
      13 RETURN_VALUE 

所以它出現在解釋提前讀取整個函數以確定是否使用a進行分配。這是記錄在任何地方?除了檢查語法錯誤外,是否還有其他任何解釋器向前看的場合?

在下文中,參考手冊的複合語句部分陳述對於函數定義:

函數定義是一個可執行語句。它的執行將當前局部命名空間中的函數名稱綁定到函數對象(函數的可執行代碼的包裝器)。

具體而言,其所結合的名稱f到包含經編譯的代碼,f.__code__,即dis prettifies我們的一個功能對象。

相關問題