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 模組,然後修改這個模組中對應的屬性。