Python的名字繫結

pythontab發表於2015-07-07

Python的名字繫結

在Python中,物件是透過名字進行關聯和引用的。Python透過名字繫結操作來引入名字。

Python中的所謂的程式碼塊就是一段作為執行單元的程式。比如:模組、函式、類定義。在互動式環境中輸入的命令也是程式碼塊的一種。一個Python指令碼檔案也是一個程式碼塊。還有就是,當我們在命令列上使用-c選項指定的命令也是一個程式碼塊。傳遞給內建函式eval()和exec()的字串引數也是程式碼塊的一種。

程式碼塊是以執行幀的方式被執行的,一個執行幀包含了一些管理資訊,可以用於除錯。執行幀還會在執行完當前的程式碼塊以後指定在何處,以怎樣的方式執行接下來的程式碼。

Python中的作用域定義了名字在程式碼塊中的可見性。如果在程式碼塊中定義了一個區域性變數,那麼這個區域性變數的作用域就是所在的這個程式碼塊。如果這個定義發生在函式體內,則這個變數的作用域就擴充套件到包含在這個函式中的任何程式碼塊中,但是,如果包含在這個函式中的一個程式碼塊中,同樣的名字被繫結到了不同的物件上,那麼外面的名字將不能被擴充套件到這個程式碼塊中。

def out_func():
    #a的作用域在out_func這個函式中
    a = 0
    b = 0
    def in_func():
    #a的作用域從out_func擴充套件到了in_func中,因為in_func這個程式碼塊包含在out_func中
    print(a)
    #out_func函式中的b不能擴充套件到in_func中,因為在in_func中,b重新繫結到了不同的物件上,所以在out_func中的b的作用域不能擴充套件到in_func中。
    b = 1

在Python中,定義在類程式碼塊中名字只能在類中可見,並且類中的名字的作用域不能擴充套件到類中的方法中。如果在類定義中出現了生成器表示式和列表展開,那麼類中的名字也不能擴充套件到這些表示式中,因為列表展開和生成器表示式的實現都是使用函式作用域的。

class C:
    a = 0
    # 在列表表示式中,a會因為未定義而丟擲NameError異常
    b = list(a + i for i in range(10))
    def method(self):
        #由於定義在類中的名字不能擴充套件到方法中,所以下面的語句是錯誤的,會丟擲a未定義的NameError異常
        print(a)

當在一個程式碼塊中使用一個名字的時候,會對最近的外圍作用域進行解析,以查詢這個名字。所有的這些在當前程式碼塊中可見的作用域的集合,稱為

當前的程式碼塊的環境。

名字繫結和作用域的關係

如果一個名字繫結到一個程式碼塊中,除非這個名字宣告為nonlocal(nonlocal宣告的作用是:使得變數在外圍作用域中,在全域性作用域之前被解析),否則這個名字就是這個程式碼塊的區域性變數。如果一個名字被繫結到模組級別,則這個名字的作用域是全域性的,這個變數是全域性變數(模組中的變數,對於模組而言是區域性變數,而對於模組中的程式碼塊而言,則是全域性變數)。如果一個名字在一個程式碼塊中使用,但是不是在這個程式碼塊中被定義的,則這個變數就是一個自由變數。

名字繫結相關的異常

如果在進行名字查詢的時候,名字沒有被找到,則會丟擲一個 NameError 異常,如果名字引用的是一個區域性變數,但是這個名字還沒有被繫結到這個區域性變數,則會丟擲一個 UnboundLocalError 異常(UnboundLocalError 是 NameError的子類)。

發生名字繫結行為的情況

發生名字繫結的行為主要有:

通常的給函式傳遞引數的時候,引數名會和傳遞過來的物件進行繫結

使用import語句進行匯入的時候,其中 from ... import * 語句會將被匯入的模組中的所有可以被匯入的名字進行繫結操作

類定義的時候

函式定義的時候

進行賦值操作的時候

在for迴圈的for語句中

在with語句中的as後面

在expect語句中的as後面

Python中的名字繫結的Pitfall

在Python中,名字繫結的一些規則,會導致在使用名字的時候,出現不能理解的錯誤,特別是對於有C、C++ 和 Java經驗的使用者。

在Python中,名字繫結操作無論發生在當前塊的 任何 位置,在這個程式碼塊中對這個名字的引用都會使用在當前塊中繫結的物件。那麼,問題就來了,如果我們在名字繫結操作發生之前對這個名字進行了引用,那麼就會出現錯誤,丟擲 UnboundLocalError 異常。

>>> a = 10
>>> def function():
print(a)
a = 20# a的繫結操作發生在print之前
>>> function()
Traceback (most recent call last):
  File "<pyshell#5>", line 1, in <module>
    function()
  File "<pyshell#4>", line 2, in function
    print(a)
UnboundLocalError: local variable 'a' referenced before assignment

在Python中,程式碼塊中的區域性變數可以透過掃描整個程式碼塊來獲得繫結的名字,所以在上面的程式碼中,a這個名字在執行print的時候透過對程式碼塊的掃描已經被找到,但是名字a的繫結操作卻還沒有發生,所以出現了錯誤。

在上面的程式碼中,如果我們需要外面定義的全域性變數a,則可以使用global 語句進行宣告。

>>> a = 10
>>> def function():
global a
print(a)
a = 20#這裡並不引入新的名字,而是將全域性變數a繫結到20上
>>> function()
10
>>> a
20

global 語句的作用是,使得後面對透過這條語句宣告的物件的引用,使用的是頂層名字空間中的名字。在頂層名字空間中,包含了全域性名字空間和內建名字空間,全域性名字空間會首先被搜尋,如果沒有找到,會對內建名字空間進行搜尋。global 語句必須出現在名字使用之前。

如果在外圍作用域中的自由變數包含了一個global宣告,則這個自由變數被認為是全域性的。

內建名字空間

在查詢內建名字空間的時候,會訪問當前程式碼塊的全域性名字空間中的 __builtins__名字,這個名字引用的是一個名字字典或者是一個模組。在 __main__ 模組中, __builtins__ 的引用是內建模組 builtins,然而,如果是在其他模組中, __builtins__ 引用的是 builtins 模組的名字字典。

注意:

CPython的實現中,不能手動修改 __builtins__ 這個變數,如果需要覆蓋這個內建名字空間中的名字,需要匯入 builtins 模組,然後修改這個模組中相應的屬性。


相關文章