Python UnboundLocalError和NameError錯誤根源解析

来源:https://www.cnblogs.com/yssjun/archive/2018/10/31/9873689.html
-Advertisement-
Play Games

如果代碼風格相對而言不是那麼的pythonic,或許很少碰到這類錯誤。當然並不是不鼓勵使用一些python語言的技巧。如果遇到這這種類型的錯誤,說明我們對python中變數引用相關部分有不當的認識和理解。而這又是對理解python相關概念比較重要的。這也是本文寫作的原因。 本文為理解閉包相關概念的做 ...


如果代碼風格相對而言不是那麼的pythonic,或許很少碰到這類錯誤。當然並不是不鼓勵使用一些python語言的技巧。如果遇到這這種類型的錯誤,說明我們對python中變數引用相關部分有不當的認識和理解。而這又是對理解python相關概念比較重要的。這也是本文寫作的原因。

 本文為理解閉包相關概念的做鋪墊,後續會詳細深入的整理出閉包相關的博文,敬請關註。

1.案例分析

在整理閉包相關概念的過程中,經常發現UnboundLocalError和NameError這兩個錯誤,剛開始遇到的時候可能很困惑,對這樣的錯誤無從下手。

1.1 案例一:

1 def outer_func():
2     loc_var = "local variable"
3     def inner_func():
4         loc_var += " in inner func"
5         return loc_var
6     return inner_func
7 
8 clo_func = outer_func()
9 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 案例二:

1 def get_select_desc(name, flag, is_format = True):
2     if flag:
3         sel_res = 'Do select name = %s' % name
4     return sel_res if is_format else name
5 
6 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 案例三:

 1 def outer_func(out_flag):
 2     if out_flag:
 3         loc_var1 = 'local variable with flag'
 4     else:
 5         loc_var2 = 'local variable without flag'
 6     def inner_func(in_flag):
 7         return loc_var1 if in_flag else loc_var2
 8     return inner_func
 9 
10 clo_func = outer_func(True)
11 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類型的錯誤或許還好理解一些:

1 my_function()
2 def my_function():
3     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. 引申案例

下麵再來看一個例子:

1 import sys
2 
3 def add_path(new_path):
4     path_list = sys.path
5 
6     if new_path not in path_list:
7         import sys
8         sys.path.append(new_path)
9 add_path('./')

平時不經意間可能就會犯上面的這個錯誤,這也是一個典型的UnboundLocalError錯誤。如果仔細的閱讀並理解上面的分析過程,相信應給能夠理解這個錯誤的原因。如果還不太清除,請再閱讀一遍 :-)

如果有什麼疑問歡迎討論。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.邏輯運算 || && ! 1||2 5&&4 !0 || 遇到第一個為true的數字就終止並返回 && 遇到第一個為false的值 就終止 返回false的值,如果沒有false就返回最後那個數 ||和&&在一起 &&的優先順序比較高 高於|| 2.作用域的問題 1.Js代碼如何執行 2.js環境 ...
  • 1、中文和英文: ^[a-zA-Z\u4E00-\u9FA5]+$ 2、中文、英文、數字: ^[a-zA-Z0-9\u4e00-\u9fa5]+$ 3、純數字: ^[0-9]*$ 4、電話(座機和手機號): (^([0-9]{3,4}-)?[0-9]{7,8}$)|(^(0|86|17951)?(1... ...
  • 前陣子用了bootstrapvalidate寫了一個登錄驗證,這裡小記一筆 首先需要引入 bootstrapValidator.css //可不引入 jquery-2.1.0.min.js bootstrap.min.js bootstrapValidator.js 下麵是校驗方式 其中校驗的兩個文 ...
  • 最近在看廖雪峰的JS課程,瀏覽器中的操作DOM的那一章,有這樣一道題。 JavaScript Swift HTML ANSI C CSS DirectX 把與Web開發技術不相關的節點刪掉。 我的答案過於複雜,我上網百度後,發現了這樣的解法。 js 'use strict'; var parent ...
  • 學習jQuery源碼,我主要是通過妙味視頻上學習的。這裡將所有的源碼分析,還有一些自己弄懂過程中的方法及示例整理出來,供大家參考。 我用的jquery v2.0.3版本。 正則表達式的分析: 解析: 判斷是否為HTML標簽或 id,例如<div>或 top x|y 表示匹配x或者y 這裡 ...
  • javascript // js源碼在構造函數創建完就自動生成了 function Aaa(){}; Aaa.prototype.constructor = Aaa; var a = new Aaa(); alert(a.constructor); // function Aaa(){}; Aaa. ...
  • 效果圖: 預設顯示最新一條數據: 點擊顯示所有數據: 代碼: 說明:這裡主要是 這塊用來控制顯示或者隱藏 根據當前點擊的 這個方法里傳遞的index 對應 isShow 數組裡的index ,對應起來後,取到數組裡的true/false值,控制列表的顯示和隱藏 說明:isShow這個數組就是添加所有 ...
  • 原文地址: "https://www.cnblogs.com/baibaomen/p/sso.html" <! h1, h2 { padding left: 2rem; color: rgb(71, 91, 204); } h1.title { font size: 22px !important; ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...