溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Python UnboundLocalError和NameError錯(cuò)誤根源案例解析

發(fā)布時(shí)間:2020-09-09 12:35:16 來源:腳本之家 閱讀:171 作者:alpha_panda 欄目:開發(fā)技術(shù)

如果代碼風(fēng)格相對(duì)而言不是那么的pythonic,或許很少碰到這類錯(cuò)誤。當(dāng)然并不是不鼓勵(lì)使用一些python語言的技巧。如果遇到這這種類型的錯(cuò)誤,說明我們對(duì)python中變量引用相關(guān)部分有不當(dāng)?shù)恼J(rèn)識(shí)和理解。而這又是對(duì)理解python相關(guān)概念比較重要的。這也是本文寫作的原因。

 本文為理解閉包相關(guān)概念的做鋪墊,后續(xù)會(huì)詳細(xì)深入的整理出閉包相關(guān)的博文,敬請(qǐng)關(guān)注。

1.案例分析

在整理閉包相關(guān)概念的過程中,經(jīng)常發(fā)現(xiàn)UnboundLocalError和NameError這兩個(gè)錯(cuò)誤,剛開始遇到的時(shí)候可能很困惑,對(duì)這樣的錯(cuò)誤無從下手。

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()

錯(cuò)誤提示:

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)

錯(cuò)誤提示:

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)

  錯(cuò)誤提示:

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

 上面的三個(gè)例子可能顯得有點(diǎn)矯揉造作,但是實(shí)際上類似錯(cuò)誤的代碼都或多或少可以在上面的例子中找到影子。這里僅僅為了說明相關(guān)概念,對(duì)例子本身的合理性不必做過多的關(guān)注。

2.錯(cuò)誤原因

由于python中沒有變量、函數(shù)或者類的聲明概念。按照C或者C++的習(xí)慣編寫python,或許很難發(fā)現(xiàn)錯(cuò)誤的根源在哪。

首先看一下這類錯(cuò)誤的官方解釋:

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.

大概意思是:

如果引用了某個(gè)變量,但是變量名沒有找到,該類型的錯(cuò)誤就是NameError。如果該名字是一個(gè)還沒有被綁定的局部變量名,那么該類型的錯(cuò)誤是NameError中的UnboundLocalError錯(cuò)誤。

下面的這種NameError類型的錯(cuò)誤或許還好理解一些:

 my_function()
 def my_function():
   pass

 如果說python解釋器執(zhí)行到def my_function()時(shí)才綁定到my_function,而my_function此時(shí)也表示的是內(nèi)存中函數(shù)執(zhí)行的入口。因此在此之前使用my_function均會(huì)有NameError錯(cuò)誤。

那么上面的例子中使用變量前,都有賦值操作(可視為一種綁定操作,后面會(huì)講),為什么引用時(shí)會(huì)出錯(cuò)?定義也可判斷可見性

如果說是因?yàn)橘x值操作沒有執(zhí)行,那么為什么該變量名在局部命名空間是可見的?(不可見的話,會(huì)有這類錯(cuò)誤:NameError: global name 'xxx' is not defined,根據(jù)UnboundLocalError定義也可判斷可見性)

問題到底出在哪里?怎樣正確理解上面三個(gè)例子中的錯(cuò)誤?

3. 可見性與綁定

簡(jiǎn)單起見,這里不介紹命名空間與變量查找規(guī)則LGB相關(guān)的概念。

在C或者C++中,只要聲明并定義了一個(gè)變量或者函數(shù),便可以直接使用。但是在Python中要想引用一個(gè)name,該name必須要可見而且是綁定的。

先了解一下幾個(gè)概念:

1.code block:作為一個(gè)單元(Unit)被執(zhí)行的一段python程序文本。例如一個(gè)模塊、函數(shù)體和類的定義等。
2.scope:在一個(gè)code block中定義name的可見性;
3.block's environment:對(duì)于一個(gè)code block,其所有scope中可見的name的集合構(gòu)成block的環(huán)境。
4.bind name:下面的操作均可視為綁定操作 •函數(shù)的形參

•import聲明

•類和函數(shù)的定義

•賦值操作

•for循環(huán)首標(biāo)

•異常捕獲中相關(guān)的賦值變量

5.local variable:如果name在一個(gè)block中被綁定,該變量便是該block的一個(gè)local variable。
6.global variable:如果name在一個(gè)module中被綁定,該變量便稱為一個(gè)global variable。
7.free variable: 如果一個(gè)name在一個(gè)block中被引用,但沒有在該代碼塊中被定義,那么便稱為該變量為一個(gè)free variable。

Free variable是一個(gè)比較重要的概念,在閉包中引用的父函數(shù)中的局部變量是一個(gè)free variable,而且該free variable被存放在一個(gè)cell對(duì)象中。這個(gè)會(huì)在閉包相關(guān)的文章中介紹。

scope在函數(shù)中具有可擴(kuò)展性,但在類定義中不具有可擴(kuò)展性。

分析整理一下:

經(jīng)過上面的一些概念介紹我們知道了,一個(gè)變量只要在其code block中有綁定操作,那么在code block的scope中便包含有這個(gè)變量。

也就是綁定操作決定了,被綁定的name在當(dāng)前scope(如果是函數(shù)的話,也包括其中定義的scope)中是可見的,哪怕是在name進(jìn)行真正的綁定操作之前。

這里就會(huì)有一個(gè)問題,那就是如果在綁定name操作之前引用了該name,那么就會(huì)出現(xiàn)問題,即使該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.

注意上面官方描述的第一句和最后一句話。

總的來說就是在一個(gè)code block中,所有綁定操作中被綁定的name均可以視為一個(gè)local variable;但是直到綁定操作被執(zhí)行之后才可以真正的引用該name。

有了這些概念,下面逐一分析一下上面的三個(gè)案例。

4. 錯(cuò)誤解析

4.1 案例一分析

在outer_func中我們定義了變量loc_var,因?yàn)橘x值是一種綁定操作,因此loc_var具有可見性,并且被綁定到了具體的字符串對(duì)象。

但是在其中定義的函數(shù)inner_func中卻并不能引用,函數(shù)中的scope不是可以擴(kuò)展到其內(nèi)定義的所有scope中嗎?

下面在在來看一下官方的兩段文字描述:

When a name is used in a code block, it is resolved using the nearest enclosing scope.

這段話告訴我們當(dāng)一個(gè)name被引用時(shí),他會(huì)在其最近的scope中尋找被引用name的定義。顯然loc_var += " in inner func"這個(gè)語句中的loc_var會(huì)先在內(nèi)部函數(shù)inner_func中找尋name loc_var。

該語句實(shí)際上等價(jià)于loc_var = loc_var + " in inner func",等號(hào)右邊的loc_var變量會(huì)首先被使用,但這里并不會(huì)使用outer_func中定義的loc_var,因?yàn)樵诤瘮?shù)inner_func的scope中有l(wèi)oc_var的賦值操作,因此這個(gè)變量在inner_func的scope中作為inner_func的一個(gè)local variable是可見的。

但是要等該語句執(zhí)行完成,才能真正綁定loc_var。也就是此語句中我們使用了inner_func block中的被綁定之前的一個(gè)local variable。根據(jù)上面錯(cuò)誤類型的定義,這是一個(gè)UnboundLocalError.

4.2 案例二分析

在這個(gè)例子中,看上去好像有問題,但是又不知道怎么解釋。

引用發(fā)生在綁定操作之后,該變量應(yīng)該可以被正常引用。但問題就在于賦值語句(綁定操作)不一定被執(zhí)行。如果沒有綁定操作那么對(duì)變量的引用肯定會(huì)有問題,這個(gè)前面已經(jīng)解釋過了。

但是還有一個(gè)疑問可能在于,如果賦值語句沒有被執(zhí)行,那么變量在當(dāng)前block中為什么是可見的?

關(guān)于這個(gè)問題其實(shí)可以被上面的一段話解釋:The local variables of a code block can be determined by scanning the entire text of the block for name binding operations.

只要有綁定操作(不管實(shí)際有沒有被執(zhí)行),那么被綁定的name可以作為一個(gè)local variable,也就是在當(dāng)前block中是可見的。scanning text發(fā)生在代碼被執(zhí)行前。

4.2 案例三分析

這個(gè)例子主要說明了一類對(duì)free variable引用的問題。同時(shí)這個(gè)例子也展示了一個(gè)free variable的使用。

在創(chuàng)建閉包inner_func時(shí),loc_var1和loc_var2作為父函數(shù)outer_func中的兩個(gè)local variable在其內(nèi)部inner_func的scope中是可見的。返回閉包之后在閉包中被引用outer_func中的local variable將作為稱為一個(gè)free variable.

閉包中的free variable可不可以被引用取決于它們有沒有被綁定到具體的對(duì)象。

5. 引申案例

下面再來看一個(gè)例子:

 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('./')

平時(shí)不經(jīng)意間可能就會(huì)犯上面的這個(gè)錯(cuò)誤,這也是一個(gè)典型的UnboundLocalError錯(cuò)誤。如果仔細(xì)的閱讀并理解上面的分析過程,相信應(yīng)給能夠理解這個(gè)錯(cuò)誤的原因。如果還不太清除,請(qǐng)?jiān)匍喿x一遍 :-)

總結(jié)

以上所述是小編給大家介紹的Python UnboundLocalError和NameError錯(cuò)誤根源案例解析,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)億速云網(wǎng)站的支持!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI