Good, better, best, never let it rest, 'Til your good is better and your better is best。
使用Python進行業務邏輯開發,Dict與List數據結構絕對是使用最多的,今天就來聊聊List數據結構的兩種創建方式,並且簡要分析下他們的區別。
# 使用一對中括號
list_a = []# 使用內置函數
list_b = list()
SO.
這兩種方法有什麼不一樣呢?哪個會快一點呢?
我們使用timeit函數對兩種創建方式進行時間上的度量:
import timeit
size = 1000
time_1 = 0.0
time_2 = 0.0
for index in range(size):
time_1 += timeit.timeit('[]')
time_2 += timeit.timeit('list()')else:
print(time_1/time_2)
Out:0.2656637479756005
經過1000次的創建空列表的比較,使用[] 創建List對象比使用list() 能快2-3倍。
為了搞清楚他們為何差距那麼大,我們先來看看他們分別在創建對象時都幹了什麼事情。
我們使用dis模塊中的dis方法觀察下兩種方式創建對象執行的字節碼:
from dis import dis
print(dis('[]'))
1 0 BUILD_LIST 0
2 RETURN_VALUE
print(dis('list()'))
1 0 LOAD_NAME 0 (list)
2 CALL_FUNCTION 0
4 RETURN_VALUE
首先,對於 [],它是 Python 中的一組字面量(literal),像數字之類的字面量一樣,表示確切的固定值,也就是說,Python 在解析到它時,就知道它要表示一個列表,因此會直接調用解釋器中構建列表的方法(對應BUILD_LIST),來創建列表,所以是一步到位。
而對於 list(),「list」只是一個普通的名稱,並不是字面量,也就是說解釋器一開始並不認識它。因此,解釋器的第一步是要找到這個名稱(對應LOAD_NAME)。它會按照一定的順序,在各個作用域中逐一查找(局部作用域--全局作用域--內置作用域),直到找到為止,找不到則拋出NameError異常。
解釋器看到「list」之後是一對圓括號,因此第二步是把這個名稱當作可調用對象來調用,即把它當成一個函數進行調用(對 應CALL_FUNCTION)。
因此,list() 在創建列表時,需要經過名稱查找與函數調用兩個步驟,才能真正開始創建列表(註:CALL_FUNCTION 在底層還會有一些函數調用過程,才能走到跟 BUILD_LIST 相通的邏輯,此處我們忽略不計)。
至此,我們就可以回答前面的問題了:因為 list() 涉及的執行步驟更多,因此它比 [] 要慢一些。
在Python3.8時,提出了 Vectorcall(a fast calling protocol for CPython) 優化方案,並會在Python3.9時正式加入發行版。
簡單來說Vectorcall可以加速 CPython 解釋器在調用類對象時的速度,極大的提升 list()、tuple()、dict() 等主要類型的調用速度,同時它還可以被用在自定義的類上。
親測測度可以提升1倍以上,感興趣的可以安裝Python3.9體驗下。
Had func!