您好,登錄后才能下訂單哦!
如果代碼風格相對而言不是那么的pythonic,或許很少碰到這類錯誤。當然并不是不鼓勵使用一些python語言的技巧。如果遇到這這種類型的錯誤,說明我們對python中變量引用相關部分有不當的認識和理解。而這又是對理解python相關概念比較重要的。這也是本文寫作的原因。
本文為理解閉包相關概念的做鋪墊,后續會詳細深入的整理出閉包相關的博文,敬請關注。
1.案例分析
在整理閉包相關概念的過程中,經常發現UnboundLocalError和NameError這兩個錯誤,剛開始遇到的時候可能很困惑,對這樣的錯誤無從下手。
1.1 案例一:
def outer_func(): loc_var = "local variable" def inner_func(): loc_var += " in inner func" return loc_var return inner_func clo_func = outer_func() clo_func()
錯誤提示:
Traceback (most recent call last):
File "G:\Project Files\Python Test\Main.py", line 238, in <module>
clo_func()
File "G:\Project Files\Python Test\Main.py", line 233, in inner_func
loc_var += " in inner func"
UnboundLocalError: local variable 'loc_var' referenced before assignment
1.2 案例二:
def get_select_desc(name, flag, is_format = True): if flag: sel_res = 'Do select name = %s' % name return sel_res if is_format else name get_select_desc('Error', False, True)
錯誤提示:
Traceback (most recent call last):
File "G:\Project Files\Python Test\Main.py", line 247, in <module>
get_select_desc('Error', False, True)
File "G:\Project Files\Python Test\Main.py", line 245, in get_select_desc
return sel_res if is_format else name
UnboundLocalError: local variable 'sel_res' referenced before assignment
1.3 案例三:
def outer_func(out_flag): if out_flag: loc_var1 = 'local variable with flag' else: loc_var2 = 'local variable without flag' def inner_func(in_flag): return loc_var1 if in_flag else loc_var2 return inner_func clo_func = outer_func(True) print clo_func(False)
錯誤提示:
Traceback (most recent call last):
File "G:\Project Files\Python Test\Main.py", line 260, in <module>
print clo_func(False)
File "G:\Project Files\Python Test\Main.py", line 256, in inner_func
return loc_var1 if in_flag else loc_var2
NameError: free variable 'loc_var2' referenced before assignment in enclosing scope
上面的三個例子可能顯得有點矯揉造作,但是實際上類似錯誤的代碼都或多或少可以在上面的例子中找到影子。這里僅僅為了說明相關概念,對例子本身的合理性不必做過多的關注。
2.錯誤原因
由于python中沒有變量、函數或者類的聲明概念。按照C或者C++的習慣編寫python,或許很難發現錯誤的根源在哪。
首先看一下這類錯誤的官方解釋:
When a name is not found at all, a NameError exception is raised. If the name refers to a local variable that has not been bound, a UnboundLocalError exception is raised. UnboundLocalError is a subclass of NameError.
大概意思是:
如果引用了某個變量,但是變量名沒有找到,該類型的錯誤就是NameError。如果該名字是一個還沒有被綁定的局部變量名,那么該類型的錯誤是NameError中的UnboundLocalError錯誤。
下面的這種NameError類型的錯誤或許還好理解一些:
my_function() def my_function(): pass
如果說python解釋器執行到def my_function()時才綁定到my_function,而my_function此時也表示的是內存中函數執行的入口。因此在此之前使用my_function均會有NameError錯誤。
那么上面的例子中使用變量前,都有賦值操作(可視為一種綁定操作,后面會講),為什么引用時會出錯?定義也可判斷可見性
如果說是因為賦值操作沒有執行,那么為什么該變量名在局部命名空間是可見的?(不可見的話,會有這類錯誤:NameError: global name 'xxx' is not defined,根據UnboundLocalError定義也可判斷可見性)
問題到底出在哪里?怎樣正確理解上面三個例子中的錯誤?
3. 可見性與綁定
簡單起見,這里不介紹命名空間與變量查找規則LGB相關的概念。
在C或者C++中,只要聲明并定義了一個變量或者函數,便可以直接使用。但是在Python中要想引用一個name,該name必須要可見而且是綁定的。
先了解一下幾個概念:
1.code block:作為一個單元(Unit)被執行的一段python程序文本。例如一個模塊、函數體和類的定義等。
2.scope:在一個code block中定義name的可見性;
3.block's environment:對于一個code block,其所有scope中可見的name的集合構成block的環境。
4.bind name:下面的操作均可視為綁定操作 •函數的形參
•import聲明
•類和函數的定義
•賦值操作
•for循環首標
•異常捕獲中相關的賦值變量
5.local variable:如果name在一個block中被綁定,該變量便是該block的一個local variable。
6.global variable:如果name在一個module中被綁定,該變量便稱為一個global variable。
7.free variable: 如果一個name在一個block中被引用,但沒有在該代碼塊中被定義,那么便稱為該變量為一個free variable。
Free variable是一個比較重要的概念,在閉包中引用的父函數中的局部變量是一個free variable,而且該free variable被存放在一個cell對象中。這個會在閉包相關的文章中介紹。
scope在函數中具有可擴展性,但在類定義中不具有可擴展性。
分析整理一下:
經過上面的一些概念介紹我們知道了,一個變量只要在其code block中有綁定操作,那么在code block的scope中便包含有這個變量。
也就是綁定操作決定了,被綁定的name在當前scope(如果是函數的話,也包括其中定義的scope)中是可見的,哪怕是在name進行真正的綁定操作之前。
這里就會有一個問題,那就是如果在綁定name操作之前引用了該name,那么就會出現問題,即使該name是可見的。
If a name binding operation occurs anywhere within a code block, all uses of the name within the block are treated as references to the current block. This can lead to errors when a name is used within a block before it is bound. This rule is subtle. Python lacks declarations and allows name binding operations to occur anywhere within a code block. The local variables of a code block can be determined by scanning the entire text of the block for name binding operations.
注意上面官方描述的第一句和最后一句話。
總的來說就是在一個code block中,所有綁定操作中被綁定的name均可以視為一個local variable;但是直到綁定操作被執行之后才可以真正的引用該name。
有了這些概念,下面逐一分析一下上面的三個案例。
4. 錯誤解析
4.1 案例一分析
在outer_func中我們定義了變量loc_var,因為賦值是一種綁定操作,因此loc_var具有可見性,并且被綁定到了具體的字符串對象。
但是在其中定義的函數inner_func中卻并不能引用,函數中的scope不是可以擴展到其內定義的所有scope中嗎?
下面在在來看一下官方的兩段文字描述:
When a name is used in a code block, it is resolved using the nearest enclosing scope.
這段話告訴我們當一個name被引用時,他會在其最近的scope中尋找被引用name的定義。顯然loc_var += " in inner func"這個語句中的loc_var會先在內部函數inner_func中找尋name loc_var。
該語句實際上等價于loc_var = loc_var + " in inner func",等號右邊的loc_var變量會首先被使用,但這里并不會使用outer_func中定義的loc_var,因為在函數inner_func的scope中有loc_var的賦值操作,因此這個變量在inner_func的scope中作為inner_func的一個local variable是可見的。
但是要等該語句執行完成,才能真正綁定loc_var。也就是此語句中我們使用了inner_func block中的被綁定之前的一個local variable。根據上面錯誤類型的定義,這是一個UnboundLocalError.
4.2 案例二分析
在這個例子中,看上去好像有問題,但是又不知道怎么解釋。
引用發生在綁定操作之后,該變量應該可以被正常引用。但問題就在于賦值語句(綁定操作)不一定被執行。如果沒有綁定操作那么對變量的引用肯定會有問題,這個前面已經解釋過了。
但是還有一個疑問可能在于,如果賦值語句沒有被執行,那么變量在當前block中為什么是可見的?
關于這個問題其實可以被上面的一段話解釋:The local variables of a code block can be determined by scanning the entire text of the block for name binding operations.
只要有綁定操作(不管實際有沒有被執行),那么被綁定的name可以作為一個local variable,也就是在當前block中是可見的。scanning text發生在代碼被執行前。
4.2 案例三分析
這個例子主要說明了一類對free variable引用的問題。同時這個例子也展示了一個free variable的使用。
在創建閉包inner_func時,loc_var1和loc_var2作為父函數outer_func中的兩個local variable在其內部inner_func的scope中是可見的。返回閉包之后在閉包中被引用outer_func中的local variable將作為稱為一個free variable.
閉包中的free variable可不可以被引用取決于它們有沒有被綁定到具體的對象。
5. 引申案例
下面再來看一個例子:
import sys def add_path(new_path): path_list = sys.path if new_path not in path_list: import sys sys.path.append(new_path) add_path('./')
平時不經意間可能就會犯上面的這個錯誤,這也是一個典型的UnboundLocalError錯誤。如果仔細的閱讀并理解上面的分析過程,相信應給能夠理解這個錯誤的原因。如果還不太清除,請再閱讀一遍 :-)
總結
以上所述是小編給大家介紹的Python UnboundLocalError和NameError錯誤根源案例解析,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對億速云網站的支持!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。