1. 程式人生 > >作用域與名稱空間的坑

作用域與名稱空間的坑

1. 名稱空間

1.1 什麼是名稱空間

Namespace名稱空間,也稱名字空間,是從名字到物件的對映。Python中,大部分的名稱空間都是由字典來實現的,但是本文的不會涉及名稱空間的實現。名稱空間的一大作用是避免名字衝突:

?
1 2 3 4 5 def fun1():    i = 1   def fun2():    i = 2

同一個模組中的兩個函式中,兩個同名名字i之間絕沒有任何關係,因為它們分屬於不同明明空間。

1.2 名稱空間的種類

常見的名稱空間有:

built-in名字集合,包括像abs()這樣的函式,以及內建的異常名字等。通常,使用內建這個詞表示這個名稱空間-內建名稱空間

模組全域性名字集合,直接定義在模組中的名字,如類,函式,匯入的其他模組等。通常,使用全域性名稱空間表示。

函式呼叫過程中的名字集合,函式中的引數,函式體定義的名字等,在函式呼叫時被“啟用”,構成了一個名稱空間。通常,使用區域性名稱空間表示。

一個物件的屬性集合,也構成了一個名稱空間。但通常使用objname.attrname的間接方式訪問屬性,而不是直接訪問,故不將其列入名稱空間討論。

類定義的名稱空間,通常直譯器進入類定義時,即執行到class ClassName:語句,會新建一個名稱空間。(見官方對類定義的說明)

1.3 名稱空間的生命週期

不同型別的名稱空間有不同的生命週期:

內建名稱空間,在Python直譯器啟動時建立,直譯器退出時銷燬;

全域性名稱空間,模組的全域性名稱空間在模組定義被直譯器讀入時建立,直譯器退出時銷燬;

區域性名稱空間,這裡要區分函式以及類定義。函式的區域性名稱空間,在函式呼叫時建立,函式返回或者由未捕獲的異常時銷燬;類定義的名稱空間,在直譯器讀到類定義建立,類定義結束後銷燬。(關於類定義的名稱空間,在類定義結束後銷燬,但其實類物件就是這個名稱空間內容的包裝,見官方對類定義的說明)

2. 作用域

2.1 什麼是作用域

作用域是Python的一塊文字區域,這個區域中,名稱空間可以被“直接訪問”。這裡的直接訪問指的是試圖在名稱空間中找到名字的絕對引用(非限定引用)。這裡有必要解釋下直接引用和間接引用:

直接引用;直接使用名字訪問的方式,如name,這種方式嘗試在名字空間中搜索名字name。

間接引用;使用形如objname.attrname的方式,即屬性引用,這種方式不會在名稱空間中搜索名字attrname,而是搜尋名字objname,再訪問其屬性。

2.2 與名稱空間的關係

現在,名稱空間持有了名字。作用域是Python的一塊文字區域,即一塊程式碼區域,需要程式碼區域引用名字(訪問變數),那麼必然作用域與名稱空間之間就有了聯絡。

顧名思義,名字作用域就是名字可以影響到的程式碼文字區域,名稱空間的作用域就是這個名稱空間可以影響到的程式碼文字區域。那麼也存在這樣一個程式碼文字區域,多個名稱空間可以影響到它。
作用域只是文字區域,其定義是靜態的;而名字空間卻是動態的,只有隨著直譯器的執行,名稱空間才會產生。那麼,在靜態的作用域中訪問動態名稱空間中的名字,造成了作用域使用的動態性。

那麼,可以這樣認為:

靜態的作用域,是一個或多個名稱空間按照一定規則疊加影響程式碼區域;執行時動態的作用域,是按照特定層次組合起來的名稱空間。

在一定程度上,可以認為動態的作用域就是名稱空間。在後面的表述中,我會把動態的作用域與其對應名稱空間等同起來。

2.3 名字搜尋規則

在程式中引用了一個名字,Python是怎樣搜尋到這個名字呢?

在程式執行時,至少存在三個名稱空間可以被直接訪問的作用域:

Local
首先搜尋,包含區域性名字的最內層(innermost)作用域,如函式/方法/類的內部區域性作用域;

Enclosing
根據巢狀層次從內到外搜尋,包含非區域性(nonlocal)非全域性(nonglobal)名字的任意封閉函式的作用域。如兩個巢狀的函式,內層函式的作用域是區域性作用域,外層函式作用域就是內層函式的 Enclosing作用域;

Global
倒數第二次被搜尋,包含當前模組全域性名字的作用域;

Built-in
最後被搜尋,包含內建名字的最外層作用域。

程式執行時,LGB三個作用域是一定存在的,E作用域不一定存在;若程式是這樣的:

?
1 2 i = 1 print (i)

區域性作用域在哪裡呢?我們認為(Python Scopes And Namespaces):

Usually, the local scope references the local names of the (textually) current function. Outside functions, the local scope references the same namespace as the global scope: the module's namespace. Class definitions place yet another namespace in the local scope.

一般地,區域性作用域引用函式中定義的名字。函式之外,區域性作用域和全域性作用域引用同一個名稱空間:模組的明星空間。然而型別的區域性作用域引用了類定義新的名稱空間。

Python按照以上L-E-G-B的順序依次在四個作用域搜尋名字。沒有搜尋到時,Python丟擲NameError異常。

2.4 何時引入作用域我們知道:

我們知道:

在Python中一個名字只有在定義之後,才能引用。

?
1 print (i)

直接引用未定義的名字i,按照搜尋規則,在LGB三個作用域均沒有搜尋到名字i(LB相同名稱空間)。丟擲NameError異常:

?
1 2 3 4 Traceback (most recent call last):   File "scope_test.py" , line 15 , in <module>    print (i) NameError: name 'i' is not defined

那對於這段程式碼呢?

?
1 2 3 4 5 6 def try_to_define_name():    '''函式中定義了名字i,並綁定了一個整數物件1'''    i = 1   try_to_define_name() print (i) #引用名字i之前,呼叫了函式

在引用名字i之前,明明呼叫了函式,定義了名字i,可是還是找不到這個名字:

?
1 2 3 4 Traceback (most recent call last):   File "scope_test.py" , line 20 , in <module>    print (i) #引用名字i之前,呼叫了函式 NameError: name 'i' is not defined

雖然定義了名字i,但是定義在了函式的區域性作用域對應的區域性名稱空間中,按照LEGB搜尋規則,在全域性作用域中自然訪問不到區域性作用域;再者,函式呼叫結束後,這個名稱空間被銷燬了。

引用名字總是與作用域相關的,因此:

在Python中一個名字只有在定義之後,才能在合適的作用域引用。

那麼,在定義名字時,就要注意名字定義的作用域了,以免定義後需要訪問時卻找不到。所以,瞭解Python在何時會引入新的作用域很有必要。一般來說,B,G兩個作用域的引入在不能夠通過程式碼操作的,能夠通過語句引入的作用域只有E,L了。Python中引入新作用域的語句很有限,總的來說只有兩類一個:

函式定義引入local作用域或者Enclosing作用域;本質上,lambda和生成器表示式也是函式,會引入新作用域。

類定義引入local作用域;

列表推導式引入local作用域,傳說在python2中列表推導式不引入新的作用域

幾個會讓有其他高階語言經驗的猿困惑的地方:

if語句:

?
1 2 3 if True :    i = 1 print (i) # output: 1,而不是NameError

if語句並不會引入新的作用域,所以名字繫結語句i = 1與print(i)是在同一個作用域中。

for語句:

?
1 2 3 for i in range ( 6 ):    pass print (i) #output: 5,而不是NameError

for語句同樣不會引入新的作用域,所以名字i的繫結和重繫結與print(i)在同一個作用域。這一點Python就比較坑了,因此寫程式碼時切忌for迴圈名字要與其他名字不重名才行。

import語句:

?
1 2 3 4 5 6 def import_sys():    '''import sys module'''    import sys   import_sys() print (sys.path) # NameError: name 'sys' is not defined

這個算非正常程式設計師的寫法了,import語句在函式import_sys中將名字sys和對應模組繫結,那sys這個名字還是定義在區域性作用域,跟上面的例子沒有任務區別。要時刻切記Python的名字,物件,這個其他程式語言不一樣,但是:

打破第一程式語言認知的第二門程式語言,才是值得去學的好語言。

3. 作用域應用

3.1 自由變數可讀不可寫

我不太想用“變數”這個詞形容名字,奈何變數是家喻戶曉了,Python中的自由變數:

?
1 If a variable is used in a code block but not defined there, it is a free variable.

如果引用發生的程式碼塊不是其定義的地方,它就是一個自由變數。專業一點,就是:

引用名字的作用域中沒有這個名字,那這個名字就是自由名字

Note: “自由名字”只是作者YY的,並沒得到廣泛認可。

我們已經瞭解了作用域有LEGB的層次,並按順序搜尋名字。按照搜尋順序,當低層作用域不存在待搜尋名字時,引用高層作用域存在的名字,也就是自由名字:

[示例1]

?
1 2 3 4 5 def low_scope():    print(s)   s = 'upper scope' low_scope()

很清楚,這段程式碼的輸出是upper scope。

[示例2]

?
1 2 3 4 5 6 def low_scope():    s = 'lower scope'   s = 'upper scope' low_scope() print(s)

很遺憾,最後的列印語句沒有按照期待打印出lower scope而是列印了upper scope。

?
1 A special quirk of Python is that – if no global statement is in effect – assignments to names always go into the innermost scope.

Python的一個怪癖是,如果沒有使用global語句,對名字的賦值語句通常會影響最內層作用域。
即賦值語句影響區域性作用域,賦值語句帶來的影響是繫結或重繫結,但是在當前區域性作用域的名稱空間中,並沒有s這個名字,因此賦值語句在區域性作用於定義了同名名字s,這與外層作用域中的s並不衝突,因為它們分屬不同名稱空間。
這樣,全域性作用域的s沒有被重繫結,結果就很好解釋了。

當涉及可變物件時,情況又有所不同了:

[示例3]

?
1 2 3 4 5 6 def low_scope():    l[0] = 2   l = [1, 2] low_scope() print(l) # [2, 2]

很遺憾,最後的列印語句沒有按照期待輸出[1, 2]而是輸出了[2, 2]。
上一個例子的經驗並不能運用在此,因為list作為一個可變物件,l[0] = 2並不是對名字l的重繫結,而是對l的第一個元素的重繫結,所以沒有新的名字被定義。因此在函式中成功更新了全域性作用於中l所引用物件的值。

注意,下面的示例跟上面的是不一樣的:

[示例4]

?
1 2 3 4 5 6 def low_scope():    l = [2, 2]   l = [1, 2] low_scope() print(l) # [1, 2]

我們可以用本節中示例1的方法解釋它。

綜上,可以認為:

自由變數可讀不可寫。

3.2 global和nonlocal

總是存在打破規則的需求:

在低層作用域中需要重繫結高層作用域名字,即通過自由名字重繫結。

於是global語句和nonlocal語句因運而生。

?
1 2 global_stmt ::= "global" identifier ("," identifier)* The global statement is a declaration which holds for the entire current code block. It means that the listed identifiers are to be interpreted as globals. It would be impossible to assign to a global variable without global, although free variables may refer to globals without being declared global.

global語句是適用於當前程式碼塊的宣告語句。列出的識別符號被解釋為全域性名字。雖然自由名字可以不被宣告為global就能引用全域性名字,但是不使用global關鍵字繫結全域性名字是不可能的。

?
1 2 nonlocal_stmt ::= "nonlocal" identifier ("," identifier)* The nonlocal statement causes the listed identifiers to refer to previously bound variables in the nearest enclosing scope excluding globals. This is important because the default behavior for binding is to search the local namespace first. The statement allows encapsulated code to rebind variables outside of the local scope besides the global (module) scope.

nonlocal語句使得列出的名字指向最近封閉函式中繫結的名字,而不是全域性名字。預設的繫結行為會首先搜尋區域性作用域。nonlocal語句使得在內層函式中重繫結外層函式作用域中的名字成為可能,即使同名的名字存在於全域性作用域。

經典的官方示例:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def scope_test():        def do_local():      spam = 'local spam'      def do_nonlocal():      nonlocal spam # 當外層作用域不存在spam名字時,nonlocal不能像global那樣自作主張定義一個      spam = 'nonlocal spam' # 自由名字spam經nonlocal聲明後,可以做重繫結操作了,可寫的。      def do_global():      global spam # 即使全域性作用域中沒有名字spam的定義,這個語句也能在全域性作用域定義名字spam      spam = 'global spam' # 自有變數spam經global聲明後,可以做重繫結操作了,可寫的。      spam = 'test spam'    do_local()    print ( "After local assignment:" , spam) # After local assignment: test spam    do_nonlocal()    print ( "After nonlocal assignment:" , spam) # After nonlocal assignment: nonlocal spam    do_global()    print ( "After global assignment:" , spam) # After global assignment: nonlocal spam     scope_test() print ( "In global scope:" , spam) # In global scope: global spam

作者說不行nonlocal的邪:

?
1 2 3 4 5 6 7 8 9 10 11 def nest_outter():    spam = 'outer'      def nest_inner():      nonlocal spam1      spam1 = 'inner'      nest_inner()    print (spam)   nest_outter()

Output:

?
1 2 3 File "scope_test.py", line 41    nonlocal spam1 SyntaxError: no binding for nonlocal 'spam1' found

4. 一些坑

作者曾經自信滿滿認為透徹瞭解了Python的作用域,但是一大堆坑踩得觸不及防。

4.1 坑1 - UnboundLocalError

?
1 2 3 4 5 6 def test():    print (i)    i = 1   i = 2 test()

Output:

?
1 2 3 4 5 6 Traceback (most recent call last):   File "scope_test.py" , line 42 , in <module>    test()   File "scope_test.py" , line 38 , in test    print (i) UnboundLocalError: local variable 'i' referenced before assignment

其實忽略掉全域性作用域中i = 2這條語句,都可以理解。

?
1 Usually, the local scope references the local names of the (textually) current function.

Python對區域性作用域情有獨鍾,直譯器執行到print(i),i在區域性作用域沒有。直譯器嘗試繼續執行後面定義了名字i,直譯器就認為程式碼在定義之前就是用了名字,所以丟擲了這個異常。如果直譯器解釋完整個函式都沒有找到名字i,那就會沿著搜尋鏈LEGB往上找了,最後找不到丟擲NameError異常。

4.2 坑2 - 類的區域性作用域

?
1 2 3 4 5 6 7 8 9 10 class Test( object ):      i = 1      def test_print( self ):      print (i)   t = Test() i = 2 t.test_print()

我就問問大家,這個輸出什麼?
當然會出乎意料輸出2了,特別是有其他語言經驗的人會更加困惑。

上文強調過:
函式名稱空間的生命週期是什麼? 呼叫開始,返回或者異常結束,雖然示例中是呼叫的方法,但其本質是呼叫類的函式。
類名稱空間的作用域是什麼?類定義開始,類完成定義結束。

類定義開始時,建立新的屬於類的名稱空間,用作區域性作用域。類定義完後,名稱空間銷燬,沒有直接方法訪問到類中的i了(除非通過間接訪問的方式:Test.i)。

方法呼叫的本質是函式呼叫:

?
1 2 3 4 5 6 7 8 9 10 11 class Test( object ):      i = 1      def test_print( self ):      print (i)   t = Test() i = 2 # t.test_print() Test.test_print(t) # 方法呼叫最後轉換成函式呼叫的方式

函式呼叫開始,其作用域與全域性作用域有了上下級關係(L和G),函式中i作為自由名字,最後輸出2。
因此,不能被類中資料成員和函式成員的位置迷惑,始終切記,Python中兩種訪問引用的方式:

直接引用:試圖直接寫名字name引用名字,Python按照搜尋LEGB作用域的方式搜尋名字。

間接引用:使用objname.attrname的方式引用名字attrname,Python不搜尋作用域,直接去物件裡找屬性。

4.3 坑3 - 列表推導式的區域性作用域

一個正常列表推導式:

?
1 2 3 a = 1 b = [a + i for i in range ( 10 )] print (b) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

現在把列表推導式放到類中:

?
1 2 3 4 5 6 7 8 class Test( object ):      a = 1    b

相關推薦

作用名稱空間

1. 名稱空間 1.1 什麼是名稱空間 Namespace名稱空間,也稱名字空間,是從名字到物件的對映。Python中,大部分的名稱空間都是由字典來實現的,但是本文的不會涉及名稱空間的實現。名稱空間的一大作用是避免名字衝突: ?

函數作用名稱空間

未定義 函數的調用 oca assign 需要 無法 () 是什麽 影響 函數作用域與名稱空間 首先我們看看下面的代碼: a = 10 def f1(): print(a) f1() 10 #不會報錯 a = 10 def f1(): print(a

Python中的變數作用名稱空間(LEGB)

Python 使⽤LEGB的順序來查詢⼀個符號對應的物件locals    ->   enclosing    function    ->   globals    ->    builtins區域性變數———閉包空間———全域性變數———內建模組a =

JavaScript踩筆記03---作用名稱空間

作用域: 作用域,顧名思義,作用的區域,也就是變數和函式的作用的區域。 作用域的作用就是隔離變數,不同作用域下同名變數不會有衝突。 舉例說明。 // 在函式fn外部定義一個字串 var str = "Hello"; function fn() { // 在函式fn內部再定義一個字串

作用命名空間

spl 操作 info 全局 自己的 生命周期 convert ssi before python命名空間的本質 一、命名空間 Python使用叫做命名空間的東西來記錄變量的軌跡。命名空間是一個 字典(dictionary) ,它的鍵就是變量名,它的值就是那些變量的值。

python 作用名稱空間

  def scope_test(): def do_local(): spam = "local spam" def do_nonlocal(): nonlocal spam spam = "nonlocal spam"

作用名稱空間

名稱空間: 用來存放名字(變數, 函式名, 類名, 引入的模組名)的 1. 全域性名稱空間: 我們在py檔案中自己寫的變數, 函式..... 2. 內建名稱空間: 我們python直譯器提供好的一些內建內容(print, input....) 3. 區域性名稱空間: 在我們執行函式的時候.會產生一個區域性

Python的變數作用名稱空間作用的區別、This inspection detects shadowing names defined in outer scopes警告解決

Python的變數作用域: L(local)區域性作用域: 區域性變數:包含在def關鍵字定義的函式中,即在函式中定義的變數。每當函式被呼叫時都會建立一個新的區域性作用域。在函式內部的變數宣告,除非特別的使用global關鍵字宣告為其全域性變數,否則均預設為區域性變數。

Jmeter學習筆記2-原件作用執行順序

校驗 height sse proc tro 有效 收集 技術分享 控制 1.元件的作用域 (1)配置元件(config elements):會影響其作用範圍內的所有元件。 (2)前置處理程序(per-processors):在其作用範圍內的每一個sampler元件之

【轉】JMeter學習(三)元件的作用執行順序

ces ner 處理器 規則 fig 子節點 控制器 conf 節點 1.元件的作用域 JMeter中共有8類可被執行的元件(測試計劃與線程組不屬於元件),這些元件中,取樣器是典型的不與其它元件發生交互作用的元件,邏輯控制器只對其子節點的取樣器有效,而其它元件(config

HTML5 | Canvas中變量作用setInterval()方法的影響

通過 value utf 出現 close span arc shadow cli Demo - 隨機繪制圓環 實現思路: 將一個圓環的繪制分成100份,setInterval()方法定義每隔時間n繪制一段新的,每份的開始路徑都是上一次的結束路徑,實現步進繪制。 通

js作用上下文

改變 mic 能力 class bsp 函數 上下 code 訪問 作用域:與調用函數,訪問變量的能力有關 作用域分為:局部和全局(在局部作用域裏可以訪問到全局作用域的變量,但在局部作用域外面就訪問不到局部作用裏面所設定的變量) 上下文:與this關鍵字有關 是調用當前可執

js面試題-----作用閉包

code 生命 結果 bin 聲明 click 函數 i++ spa 1、問題代碼: var length = 10; function fn(){ console.log(this.length); } var obj = { length:5,

js的作用作用

性能 使用 plain 賦值 function keyword ack 全局變量 pla JavaScript的作用域和作用域鏈。在初學JavaScript時,覺得它就和其他語言沒啥區別,尤其是作用域這塊,想當然的以為“全局變量就是在整個程序的任何地方都可以訪問,也就是寫在

變量作用解構賦值

方法 ber undefine 作用域 定義變量 模式 變量 bob def 在JavaScript中,用var申明的變量實際上是有作用域的。 如果一個變量在函數體內部申明,則該變量的作用域為整個函數體,在函數體外不可引用該變量: ‘use strict‘; functio

作用變量提升

ole 訪問 內置對象 function 執行 name 兩種 局部變量 () 作用域與變量提升 作用域 JS中變量的作用域有全局作用域和局部作用域兩種,作用域簡單來講就是變量與函數的可訪問範圍。 全局作用域: 1.最外層函數和最外層函數外面定義的變量。 2.未聲明

ECMAScript作用作用

eve 存在 -c iso 增加 都是 res func 定義   執行環境(execution context,我們也叫做“環境”)是定義變量和函數有權訪問的其他數據的重要概念,在JavaScript中它決定了各自的行為,在每個執行環境中與之關聯的

Jsp的四大作用九大對象

處理對象 tput rec message padding ddc tex AC init 轉載:https://www.cnblogs.com/mengzhen123/p/5968831.html 內置對象特點: 1. 由JSP規範提供,不用編寫者實例化。 2. 通過We

變量、作用內存的一些總結

ins 作用域 循環引用 dom元素 rip 兩種 efi 元素 ceo javascript變量可以用來保存兩種類型的值:基本類型值和引用類型值 基本數據類型:undefined、Null、boolean、number、string 引用類型值:object array

Spring之Bean的作用生命周期

src efi lin 控制 初始化 troy [] 分享 isp 在前面博客中提到容器啟動獲得BeanDefinition對象中有一個scope 屬性。該屬性控制著bean對象的作用域。本章節介紹Bean的作用域及生命周期,了解bean是怎麽來的又怎麽沒的。 一、Bean