python生成1到10的列表_[Python] 列表推導式和生成器表示式
技術標籤:python生成1到10的列表
我其實一直不太看得明白寫的很簡短的程式碼,在讀了幾頁《流暢的Python》後我明白了——是我太菜。週末花了點時間把自己一直不會用的列表推導式和生成器表示式好好看了下,整理一篇筆記,部分程式碼是參考了《流暢的Python》中的示例。
在搞明白這倆貨之前,如果需要從兩個資料來源取出資料耦合起來,我大概率會把程式碼寫成這樣的德行——
for x in xs:
for y in ys:
x_y = (x, y)
嗯,要寫三行,雖然確實很容易看懂意思,但確實不簡潔,再加上經常看到別人寫的程式碼裡用到列表推導式和生成器表示式,就更加難受了……
列表推導式,List Comprehension,一般縮寫為listcomps
生成器表示式,Generator Expression,一般縮寫為genexps
掌握這兩個東西,可以讓程式碼更簡短,也能讓程式碼執行更省記憶體。
List Comprehension
給定一個數字,計算這個數字的平方,然後我們要對一堆數字做這個事情,有兩個辦法——
- 使用lambda函式+map函式
- 使用列表推導式
第一個法子,程式碼會寫成這樣
print(
list(map(lambda x: x * x, range(1,11)))
)
我是不喜歡lambda的,總覺得這個寫法很難看,它等同於下面這樣子
def calc(x):
return x * x
print(
list(map(calc, range(1,11)))
)
而且,還得用上map函式,如果你沒用過map函式,比如我就用的少,經常用到的時候想不起來它的兩個引數幹嘛的……
而如果用列表推導式實現這個呢——
print(
[x * x for x in range(1,11)]
)
一行就可以了,也不需要去寫個lambda,或者寫個def去給map調了。對Python來說,lambda和列表推導式的使用原則都差不多——不能太長,可讀性都會變差,就簡短的程式碼來說,我更喜歡列表推導式,你可以直接把意思讀出來——
需要x乘以x,x 從 range(1,11) 中迴圈取出
列表推導式的外部是一箇中括號,它提醒你,其永遠是返回一個列表物件的
上面的幾個寫法的執行結果——
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
對整個列表推導式分成三部分來看——
[ list comprehension ]
,外殼為中括號,提醒你返回物件一定是個列表x * x
,對迴圈元素的處理,一個表示式,所以這裡也可以是個函式呼叫for x in range(1,11)
,一個for迴圈,x實際上是一個區域性變數,第三部分直接告訴我們x從哪取,這裡就從range(1,11)
裡取
P.S. Python2的列表推導式是有變數洩漏的問題的,換言之,py2的 x
變數可能會汙染外部的同名變數,Python3沒這個問題,Python3的列表推導式有自己的作用域了。
上面的程式碼用普通的for迴圈寫就寫成這樣了——
x_power = []
for x in range(1,11):
x_power.append(x * x)
print(x_power)
嗯,很醜。我以前都這麼寫程式碼的。
再下面這個例子來自《流暢的Python一書》
symbols = '[email protected]#$%^&*'
print([ord(symbol) for symbol in symbols])
這裡對每個元素的處理就是呼叫函式ord
再看最一開始的兩層迴圈,實際上就是笛卡爾積,用列表推導式也很好寫
colors = ['Black', 'White', 'Yellow']
sizes = ['XS', 'S', 'M', 'L', 'XL']
t_shirt_models = [(color, size) for color in colors for size in sizes]
print(t_shirt_models)
調整for迴圈的順序,即可調整實際的迴圈順序,它和兩層巢狀的迴圈是等價的
列表推導式裡還可以寫條件,比如下面這樣子
names = ['Addison', 'Lucy', 'Audrey', 'Bella', 'Nova', 'Brooklyn', 'Paisley', 'Savannah',
'Claire', 'Skylar', 'Isla', 'Genesis', 'Naomi', 'Elena', 'Caroline']
names_mt4_from_listcomps = [n for n in names if len(n) > 4]
print(names_mt4_from_listcomps)
條件直接嵌入在for迴圈後面就好了,我要取出的是名字長度大於4個字元的。
如果用lambda函式+filter函式,就得這麼寫
names_mt4_from_filter = list(filter(lambda n: len(n) > 4, names))
print(names_mt4_from_filter)
嗯,括號都得寫好幾個……
總之,列表推導式看起來就是“順著讀”就好了,所以太長也不合適啦
Generator Expression
生成器表示式寫法和列表推導式幾乎一樣——只要把中括號換成圓括號就好了,所以,襯衫顏色那個例子就變成這麼寫——
colors = ['Black', 'White', 'Yellow']
sizes = ['XS', 'S', 'M', 'L', 'XL']
t_shirt_models = ((color, size) for color in colors for size in sizes)
print(t_shirt_models)
不過,這個打印出來的就不是完整的結果了,而是——
<generator object <genexpr> at 0x0000020CE2EC4C48>
一個生成器物件。
這是因為生成器遵循迭代器協議,它是一種有“惰性”的東西,你不對它做操作,它就什麼也不做。
所以如果你要處理非常多的資料的時候,資料不需要再複製一次了,資料留在原處就好了,生成器會在“需要”的時候去取資料的,這樣就減少了記憶體的消耗。
理所當然的,你不能在I/O相關的操作裡用它,比如去資料庫讀資料,你要是每次都去讀一次,後果不堪設想……
怎麼讓資料出來呢?簡單的辦法就是for迴圈咯
for t in t_shirt_models:
print(t)
當然,用一個函式去處理生成器物件也可以,反正它是可以被“迭代”的。
對比listcomps和genexps
打斷點對比可以明顯看出兩者的差別,先來看列表推導式的——
知乎視訊www.zhihu.com列表推導式會把所有的資料都生成出來,存到記憶體裡,然後才輪到print()
函式從結果裡去取資料列印。
而生成器表示式,則是每次print
執行的時候去生成一次資料。
最後,推薦一下《流暢的Python》這本書,在你有了一定的Python基礎之後,一定要看看這本書。我踩了很多書坑,但這本書不坑。