我將示范微優化(micro optimization)如何提升python代碼5%的執行速度。5%!同時也會觸怒任何維護你代碼的人。
但實際上,這篇文章只是解釋一下你偶爾會在標準庫或者其他人的代碼中碰到的代碼。我們先看一個標準庫的例子,collections.OrderedDict類:
def __setitem__(self, key, value, dict_setitem=dict.__setitem__): if key not in self: root = self.__root last = root[0] last[1] = root[0] = self.__map[key] = [last, root, key] return dict_setitem(self, key, value)
注意最后一個參數:dict_setitem=dict.__setitem__。如果你仔細想就會感覺有道理。將值關聯到鍵上,你只需要給__setitem__傳遞三個參數:要設置的鍵,與鍵關聯的值,傳遞給內建dict類的__setitem__類方法。等會,好吧,也許最后一個參數沒什么意義。
作用域查詢
為了理解到底發生了什么,我們看下作用域。從一個簡單問題開始:在一個python函數中,如果遇到了一個名為open的東西,python如何找出open的值?
# <GLOBAL: bunch of code here> def myfunc(): # <LOCAL: bunch of code here> with open('foo.txt', 'w') as f: pass簡單作答:如果不知道GLOBAL和LOCAL的內容,你不可能確定open的值。概念上,python查找名稱時會檢查3個命名空間(簡單起見忽略嵌套作用域):
局部命名空間
全局命名空間
內建命名空間
所以在myfunc函數中,如果嘗試查找open的值時,我們首先會檢查本地命名空間,然后是全局命名空間,接著內建命名空間。如果在這3個命名空間中都找不到open的定義,就會引發NameError異常。
作用域查找的實現
上面的查找過程只是概念上的。這個查找過程的實現給予了我們探索實現的空間。
def foo(): a = 1 return a def bar(): return a def baz(a=1): return a
我們看下每個函數的字節碼:
>>> import dis>>> dis.dis(foo) 2 0 LOAD_CONST 1 (1) 3 STORE_FAST 0 (a) 3 6 LOAD_FAST 0 (a) 9 RETURN_VALUE >>> dis.dis(bar) 2 0 LOAD_GLOBAL 0 (a) 3 RETURN_VALUE >>> dis.dis(baz) 2 0 LOAD_FAST 0 (a) 3 RETURN_VALUE
注意foo和bar的區別。我們立即就可以看到,在字節碼層面,python已經判斷了什么是局部變量、什么不是,因為foo使用LOAD_FAST,而bar使用LOAD_GLOBAL。
我們不會具體闡述python的編譯器如何知道何時生成何種字節碼(也許那是另一篇文章的范疇了),但足以理解,python在執行函數時已經知道進行何種類型的查找。
另一個容易混淆的是,LOAD_GLOBAL既可以用于全局,也可以用于內建命名空間的查找。忽略嵌套作用域的問題,你可以認為這是“非局部的”。對應的C代碼大概是[1]:
|
新聞熱點
疑難解答