列表推導和生成器表達式

1 概念解釋

列表推導是構建列表的快捷方式,生成器表達式可以用來創建其他任何類型的序列。

列表推導:放在方括號裏的表達式,使用關鍵字for與in,通過處理和過濾一個或多個可迭代對象裏的元素構建列表。

生成器:使用生成器函數或生成器表達式構建的迭代器,無需迭代集合就可能生成值。生成斐波那契數列的生成器是個典型實例,這個數列是一種無窮序列。

生成器表達式:放在括號裏的表達式,句法與列表推導一樣,只是返回的是生成器(而不是列表)


2 列表推導

下面的代碼有兩部分:前一部分使用通常的做法,即使用for循環構造一個新列表;後者使用的是列表推導構建的新列表。

注:ord(‘a’),a爲Unicode字符,返回值爲字符a對應的Unicode數值;chr(n),n爲Unicode數值,返回值爲n對應的Unicode字符。

>>> symbols = '*&^%$#!'
>>> codes = []
>>> for symbol in symbols:
...     codes.append(ord(symbol))
...
>>> codes
[42, 38, 94, 37, 36, 35, 33]


>>> symbols = '*&^%$#!'
>>> codes = [ord(symbol) for symbol in symbols]
>>> codes
[42, 38, 94, 37, 36, 35, 33]

比較上面的兩段代碼,可以看出使用列表推導的代碼更短,更易於理解。爲了防止濫用列表推導,需要注意:只用列表推導來創建新的列表,而且儘量保持簡短。若列表推導的代碼超過兩行,應該考慮使用for循環重寫了。總之,這個度得自己把握好。

列表推導可以幫我們把一個序列或其他可迭代類型中的元素過濾或是加工,然後再新建一個列表。

2.1 列表推導同filter和map的比較

filter和map合起來能做的事情,列表推導也可以做,而且還不需要藉助難以理解和閱讀的lambda表達式。

下面的代碼中,分別用列表推導map/filter組合來創建相同的表單:

>>> symbols = '&^%$#@'
>>> beyond_ascii1 = [ord(s) for s in symbols if ord(s) > 38]
>>> beyond_ascii1
[94, 64]


>>> beyond_ascii2 = list(filter(lambda c: c > 38, map(ord, symbols)))
>>> beyond_ascii2
[94, 64]

2.2 笛卡爾積

笛卡爾積:兩個或兩個以上的列表的元素對構成元組,這些元組構成的列表就是笛卡爾積。

用列表推導可以生成兩個或以上的可迭代類型的笛卡爾積。笛卡爾積是一個列表,列表裏的元素是由輸入的可迭代類型的元素對構成的元組。笛卡爾積列表的長度等於輸入變量的長度的乘積。

在這裏插入圖片描述
上圖是含有4種花色和3種牌面的列表的笛卡爾積,結果是一個包含12個元素的列表。

這是一個例子:現在需要一個列表,列表裏是3種不同尺寸的T恤衫,每個尺寸都有2個顏色,下面的代碼用列表推導算出了這個列表,共6種組合:

>>> colors = ['black', 'white']
>>> sizes = ['S', 'M', 'L']
>>> tshirts = [(color, size) for color in colors for size in sizes]
>>> tshirts
[('black', 'S'), ('black', 'M'), ('black', 'L'), ('white', 'S'), ('white', 'M'), ('white', 'L')]


>>> for color in colors:
...     for size in sizes:
...         print((color, size))
...
('black', 'S')
('black', 'M')
('black', 'L')
('white', 'S')
('white', 'M')
('white', 'L')

上面的代碼有兩部分,第一部分是使用列表推導,第二部分是使用for循環。這兩部分得到的結果都是先以顏色排列,再以尺碼排列。而且兩個部分的循環嵌套關係先後順序都一樣。

列表推導的作用只有一個——生成列表。 若想生成其他類型的序列,生成器表達式就派上用場了。


3 生成器表達式

雖然也可以用列表推導來初始化元組、數組或其他序列類型,但是生成器表達式是更好的選擇。這是因爲生成器表達式背後遵守了迭代器協議,可以逐個地產出元素。生成器表達式的語法跟列表推導差不多,只是把方括號換爲圓括號。

下面的代碼是一個【用生成器表達式建立元組和數組】的例子:

>>> symbols = '*&^%$'
>>> tuple(ord(symbol) for symbol in symbols) # 1
(42, 38, 94, 37, 36)


>>> import array
>>> array.array('I', (ord(symbol) for symbol in symbols)) # 2
array('I', [42, 38, 94, 37, 36])

# 1:若生成器表達式是一個函數調用過程中的唯一參數,則不需要額外再用括號圍起來。

# 2:array的構造方法需要兩個參數,因此括號是必需的。array構造方法的第一個參數指定了數組中數字的存儲方式。

下面的代碼利用生成器表達式實現了一個笛卡爾積,用以打印出上文中我們提到過的T恤衫的2種顏色和3種尺碼的所有組合。與上文用列表推導的代碼相比,用到生成器表達式以後,內存裏不會留下一個有6個組合的列表,因爲生成器表達式會在每次for循環運行時才生成一個組合。這樣的話,可以減少運行開銷,如,要計算兩個各有1000個元素的列表的笛卡爾積,生成器表達式就可以幫忙省掉運行for循環的可銷——即一個含有100萬個元素的列表。下面請看代碼:

>>> colors = ['black', 'white']
>>> sizes = ['S', 'M', 'L']
>>> for tshirt in ('%s %s' % (c, s) for c in colors for s in sizes): # 1
...     print(tshirt)
...
black S
black M
black L
white S
white M
white L

# 1 生成器表達式逐個產出元素,從來不會一次性產出一個含有6個T恤樣式的列表


4 字典推導

字典推導可以從任何以鍵值對作爲元素的可迭代對象中構建出字典。下面的代碼是一個例子,展示了利用字典推導可以把一個裝滿元組的列表編程兩個不同的字典:

>>> DIAL_CODES = [(86, 'China'), (91, 'India'), (62, 'Indonesia'), (55, 'Brazil')] # 元組列表
>>> country_code = {country: code for code, country in DIAL_CODES}
>>> country_code
{'China': 86, 'India': 91, 'Indonesia': 62, 'Brazil': 55}
>>> {code: country.upper() for country, code in country_code.items() if code < 66}
{62: 'INDONESIA', 55: 'BRAZIL'}

5 集合推導

集合推導用於構成一個集合。下面的代碼是一個例子:新建一個Latin-1字符集合,該集合裏的每個字符的Unicode名字裏都有’SIGN’這個單詞。代碼如下:

>>> from unicodedata import name # 1
>>> {chr(i) for i in range(32, 256) if 'SIGN' in name(chr(i), '')} # 2
{'¥', '¬', '©', '+', 'µ', '$', '°', '>', '®', '#', '¤', '§', '%', '=', '÷', '×', '£', '¶', '¢', '<', '±'}

# 1:從unicodedata模塊裏導入name函數,用於獲取字符的名字

# 2:把【編碼在32~255之間的字符】的名字裏有’SIGN’單詞的挑出來,然後放入一個集合裏。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章