Python語言有一種獨特的推導式語法,有點像語法糖,能夠幫你在某些場合寫出比較精簡酷炫的代碼,同時,它的性能可能會比咱們寫循環要好。它主要用於初始化一個列表,也能夠用於初始化集合和字典。python
列表推導式是一種快速生成列表的方式。它通常用「[]"括起來,例如數組
>>> [i for i in range(10)] [0,1, 2, 3, 4, 5, 6, 7, 8, 9]
這是一種最基本的用法,列表推導式先執行for循環,再把遍歷的元素(或者對元素的一些計算表達式)做爲列表的元素返回一個列表。app
>>> [i*i for i in range(10)] [0,1, 4, 9, 16, 25, 36, 49, 64, 81]
它就至關於ide
>>> l = [] >>> for i in range(10): ... l.append(i*i) ... >>>
咱們能夠用列表推導快速初始化一個二維數組函數
m = [[0,0,0], [0,0,0], [0,0,0] ] n = [] for row in range(3): r = [] for col in range(3): r.append(0) n.append(r) print(n)
用下面的式子就能夠獲得這個二維數組性能
>>> [[0]*3 for i in range(3)] [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
列表推導式有不少種形式優化
這種生成的元素個數不會少,只是根據for循環的結果使用不一樣的表達式code
# 若是i是5的倍數,結果是i,不然就是0 >>> [i if i % 5 == 0 else 0 for i in range(20)] [0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 10, 0, 0, 0, 0, 15, 0, 0, 0, 0] # 若是是偶數就加100,奇數就減100 >>> [i+100 if i % 2 == 0 else i-100 for i in range(10)] [100, -99, 102, -97, 104, -95, 106, -93, 108, -91]
這種會只取符合條件的元素,因此元素個數跟條件相關對象
# for循環的結果只選擇是偶數的 >>> [i for i in range(10) if i % 2 == 0] [0, 2, 4, 6, 8] # for循環的結果只選擇是2和3的倍數的 >>> [i for i in range(10) if i % 2 == 0 and i % 3 == 0] [0, 6] # for循環的結果只選擇偶數,而且應用str函數 >>> [str(i) for i in range(10) if i % 2 == 0] ['0', '2', '4', '6', '8']
假如咱們展開一個二維矩陣,以下面的m,咱們能夠用嵌套循環實現。blog
m = [[1,2,3], [4,5,6], [7,8,9] ] n = [] for row in m: for col in row: n.append(col) print(n)
用列表推導,最外層的for循環獲得的row,能夠在內層中使用
m = [[1,2,3], [4,5,6], [7,8,9] ] n = [col for row in m for col in row] print(n)
再好比下面這個例子
>>> [a + b for a in '123' for b in 'abc'] ['1a', '1b', '1c', '2a', '2b', '2c', '3a', '3b', '3c']
列表推導的用法比較靈活,咱們不必定要把全部的都掌握,可是要能看懂。
>>> dic = {"k1":"v1","k2":"v2"} >>> a = [k+":"+v for k,v in dic.items()] >>> a ['k1:v1', 'k2:v2']
集合推導的語法與列表推導同樣,只是它是用」{}「,並且,集合會自動去重
>>> { i for i in range(10)} {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} >>> { 0 if i % 2 == 0 else 1 for i in range(10)} {0, 1}
字典推導的語法也與其餘的相似,只不過在最前面的格式是key:value,並且也是會去重
>>> { i : i.upper() for i in 'hello world'} {'h': 'H', 'e': 'E', 'l': 'L', 'o': 'O', ' ': ' ', 'w': 'W', 'r': 'R', 'd': 'D'} >>> { str(i) : i*i for i in range(10)} {'0': 0, '1': 1, '2': 4, '3': 9, '4': 16, '5': 25, '6': 36, '7': 49, '8': 64, '9': 81}
既然用[]就能作列表推導,那用()是否是就能作元組推導了?不是的,由於()被用在了一種特殊的對象上:生成器(generator)。
>>> a = (i for i in range(10)) >>> print(a) <generator object <genexpr> at 0x000001A6100869C8> >>> type(a) <class 'generator'>
生成器是一個順序產生元素的對象,只能順序訪問,只能前進,並且只能遍歷一次。
可使用next()函數取下一個元素,取不到就會報StopIteration異常,也可使用for循環遍歷。
生成式無法用下標訪問,用next訪問直到報異常
>>> a = (i for i in range(0,2)) >>> a[0] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'generator' object is not subscriptable >>> next(a) 0 >>> next(a) 1 >>> next(a) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
用for循環遍歷
>>> a = (i for i in range(0,2)) >>> for i in a: ... print(i) ... 0 1
先用next訪問,再用for循環
>>> a = (i for i in range(0,3)) >>> next(a) 0 >>> for i in a: ... print(i) ... 1 2
咱們能夠加上list,tuple,set等作強轉,可是list和set就不必了,若是想初始化成tuple,就用tuple作強轉。強轉的時候不須要再加多餘的括號。
>>> a = tuple(i for i in range(0,3)) >>> a (0, 1, 2) >>> a = tuple( (i for i in range(0,3)) ) >>> a (0, 1, 2)
生成式是惰式計算的,就是你確實用到這個元素了,它纔去計算,好處就是節省了內存,可是壞處是不能隨機訪問。
咱們用timeit模塊去比較一下性能。
import timeit def getlist1(): l = [] for i in range(10000): l.append(i) return l def getlist2(): return [i for i in range(10000)] # 各執行10000次 t1 = timeit.timeit('getlist1()',"from __main__ import getlist1", number=10000) t2 = timeit.timeit('getlist2()',"from __main__ import getlist2", number=10000) print('循環方式:',t1) print('推導式方式:',t2)
執行結果以下:
循環方式: 5.343517699991935 推導式方式: 2.6003115000057733
可見循環的方式比推導式慢了一倍,爲何會有這個問題呢?咱們直接反編譯看這兩個的區別,用dis模塊能夠反編譯Python代碼,產生字節碼。
源代碼及行數以下
getlist1的反編譯以下,左邊紅色對應源代碼的行數,藍色圈內就是第6行代碼對應的字節碼,咱們能夠看到,它有一個傳參而且調用方法append的過程,調用函數的代價是比較大的。
再來看一下列表推斷的反編譯結果
首先從字節碼數量上來比列表推斷就比用循環調append要少的多,並且列表推斷沒有使用方法調用,直接用了這個指令LIST_APPEND,在Python官網上的解釋是這樣的。
實際上這個解釋是有誤導性,字節碼中使用LIST_APPEND和在Python代碼中調用append是徹底不同的,只不過這種底層的東西沒有不少人關心,它們的功能是同樣的。在2008年的時候就有人給Python代碼提patch,但願能自動將list.append()進行優化,直接優化成LIST_APPEND而不是經過函數調用,可是目前還沒被採納。
提出者但願能在編譯的時候加一些選項,好比像gcc可使用-O1,-O2等進行不一樣級別的優化,可是目前CPython是沒有這些選項的,由於大多數的Python開發者並不關心性能。
若是咱們把上面的列表換成集合或者字典,差異會更大,因此能用推導式的地方儘可能用推導式,能夠提升性能。
其實這兩個並不具有可比性,由於生成的結果並非一個東西。咱們能夠很容易的預測,產生生成器的推導式性能要好於列表推導式,可是用的時候生成器就不如列表了。
import timeit def getlist1(): return [i for i in range(10000)] def getlist2(): return (i for i in range(10000)) # 各執行10000次 t1 = timeit.timeit('getlist1()',"from __main__ import getlist1", number=10000) t2 = timeit.timeit('getlist2()',"from __main__ import getlist2", number=10000) print('列表:',t1) print('生成器:',t2) def getlist11(): a = [i for i in range(10000)] sum = 0 for i in a: sum += i def getlist22(): a = (i for i in range(10000)) sum = 0 for i in a: sum += i # 各執行10000次 t1 = timeit.timeit('getlist11()',"from __main__ import getlist11", number=10000) t2 = timeit.timeit('getlist22()',"from __main__ import getlist22", number=10000) print('列表:',t1) print('生成器:',t2)
執行結果:
列表: 2.5977418000111356 生成器: 0.006076899997424334 列表: 6.336311199993361 生成器: 9.181903699995019
生成器產生的性能遠大於列表,可是遍歷的時候不如列表,可是整體上看好像生成器好。不過不要忘了,生成器不能隨機訪問,並且只能用一次。因此這兩種對象,就是在合適的地方用合適的類型,不必定哪種比哪種更好。