Python列表推導(list comprehension)VS 生成器表達式(generator expression)


你知道如下語法之間的區別嗎?程序員

[x for x in range(5)]

(x for x in range(5))

tuple(range(5))
複製代碼

本文將向您介紹這裏的區別。express

關於列表的5個事實

首先,對列表進行簡短回顧(在其餘編程語言中一般稱爲「數組」):django

列表是一種能夠表示爲元素集合的數據。一個簡單的列表以下所示:[0, 1, 2, 3, 4, 5]
列表將全部可能類型的數據和數據組合做爲其元素:編程

>>> a = 12
>>> b = "this is text"
>>> my_list = [0, b, ['element', 'another element'], (1, 2, 3), a]
>>> print(my_list)
[0, 'this is text', ['element', 'another element'], (1, 2, 3), 12]
複製代碼

列表能夠編入索引。您可使用如下語法訪問任何單個元素或元素組:數組

>>> a = ['red', 'green', 'blue']
>>> print(a[0])
red
複製代碼

與字符串不一樣,列表在Python中是可變的。這意味着您能夠替換,添加或刪除元素。
您可使用for循環和range()函數建立列表。bash

>>> my_list = []
>>> for x in range(10):
...     my_list.append(x * 2)
... 
>>> print(my_list)
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
複製代碼

好的 - 那麼列表推導是什麼?

一般被視爲Python中函數式編程的一部分,列表推導容許您使用包含較少代碼的for循環建立列表。app

使用列表推導來查看前一個示例的實現:編程語言

>>> comp_list = [x * 2 for x in range(10)] 
>>> print(comp_list)
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
複製代碼

上面的示例過於簡單,可讓您瞭解語法。使用更簡單的list(range(0, 19, 2))功能能夠實現相同的結果。函數式編程

您還能夠在推導的第一部分中使用更復雜的修改器,或添加將過濾列表的條件。像這樣的東西:函數

>>> comp_list = [x ** 2 for x in range(7) if x % 2 == 0] 
>>> print(comp_list)
[4, 16, 36]
複製代碼

另外一個可用選項是使用列表推導來組合多個列表並建立列表列表。乍一看,語法彷佛很複雜。將列表視爲外部序列和內部序列可能會有所幫助。

當您想要經過組合兩個現有列表來建立列表列表時,是時候展現列表推導的強大功能了:

>>> nums = [1, 2, 3, 4, 5]
>>> letters = ['A', 'B', 'C', 'D', 'E']
>>> nums_letters = [[n, l] for n in nums for l in letters]
#the comprehensions list combines two simple lists in a complex list of lists.
>>> print(nums_letters)
>>> print(nums_letters)
[[1, 'A'], [1, 'B'], [1, 'C'], [1, 'D'], [1, 'E'], [2, 'A'], [2, 'B'], [2, 'C'], [2, 'D'], [2, 'E'], [3, 'A'], [3, 'B'], [3, 'C'], [3, 'D'], [3, 'E'], [4, 'A'], [4, 'B'], [4, 'C'], [4, 'D'], [4, 'E'], [5, 'A'], [5, 'B'], [5, 'C'], [5, 'D'], [5, 'E']]
>>>
複製代碼

讓咱們用文本嘗試它,或者說字符串對象是正確的。

>>> iter_string = "some text"
>>> comp_list = [x for x in iter_string if x !=" "]
>>> print(comp_list)
['s', 'o', 'm', 'e', 't', 'e', 'x', 't']
複製代碼

推導不只限於列表。您也能夠建立dicts並設置推導。

>>> dict_comp = {x:chr(65+x) for x in range(1, 11)}
>>> type(dict_comp)
<class 'dict'>  
>>> print(dict_comp)
{1: 'B', 2: 'C', 3: 'D', 4: 'E', 5: 'F', 6: 'G', 7: 'H', 8: 'I', 9: 'J', 10: 'K'}

>>> set_comp = {x ** 3 for x in range(10) if x % 2 == 0}
>>> type(set_comp)
<class 'set'>  
>>> print(set_comp)
{0, 8, 64, 512, 216}
複製代碼

Iterable和Iterator之間的區別

若是你瞭解了迭代和迭代器,那麼理解生成器的概念會更容易。

Iterable是數據的「序列」,您可使用循環迭代。可迭代的最簡單可見示例能夠是整數列表 - [1, 2, 3, 4, 5, 6, 7]。能夠迭代其餘類型的數據,如字符串,dicts,元組,集合等。

基本上,任何具備iter()方法的對象均可以用做可迭代的。您可使用hasattr()解釋器中的函數進行檢查。

>>> hasattr(str, '__iter__')
True  
>>> hasattr(bool, '__iter__')
False
複製代碼

迭代一系列數據時,就會實現迭代器協議。例如,當您使用for循環時,後臺發生如下狀況:

iter()在對象上調用第一個方法將其轉換爲迭代器對象。
在迭代器對象上調用該方法以獲取序列的下一個元素。 next()
若是StopIteration沒有要調用的元素,則會引起異常。

>>> simple_list = [1, 2, 3]
>>> my_iterator = iter(simple_list)
>>> print(my_iterator)
<list_iterator object at 0x7f66b6288630>  
>>> next(my_iterator)
1  
>>> next(my_iterator)
2  
>>> next(my_iterator)
3  
>>> next(my_iterator)
Traceback (most recent call last):  
  File "<stdin>", line 1, in <module>
StopIteration
複製代碼

生成器表達式(generator expression)

在Python中,生成器提供了一種實現迭代器協議的便捷方式。Generator是一個使用帶有yield語句的函數建立的迭代。

生成器的主要特徵是按需評估元素。當您使用return語句調用普通函數時,只要遇到return語句,函數就會終止。

在帶有yield語句的函數中,函數的狀態從上次調用中「保存」,而且能夠在下次調用生成函數時被拾取

>>> def my_gen():
...     for x in range(5):
...             yield x
複製代碼

生成器表達式容許在沒有yield關鍵字的狀況下即時建立生成器。可是它們不能分享用yield函數建立的生成器的所有功能。

語法和概念相似於列表推導的語法和概念:

>>> gen_exp = (x ** 2 for x in range(10) if x % 2 == 0) 
>>> for x in gen_exp:
...     print(x)
0  
4  
16  
36  
64
複製代碼

在語法方面,惟一的區別是你使用括號而不是方括號。

列表推導和生成器表達式返回的數據類型不一樣。

>>> list_comp = [x ** 2 for x in range(10) if x % 2 == 0]
>>> gen_exp = (x ** 2 for x in range(10) if x % 2 == 0)
>>> print(list_comp)
[0, 4, 16, 36, 64]
>>> print(gen_exp)
<generator object <genexpr> at 0x7f600131c410>
複製代碼

生成器在列表中的主要優勢是它佔用的內存要少得多。咱們可使用sys.getsizeof()方法檢查兩種類型佔用的內存量。

注意:在Python 2中,使用range()函數實際上沒法反映大小方面的優點,由於它仍然將整個元素列表保存在內存中。可是,在Python 3中,這個例子是可行的,由於它range()返回一個範圍對象。

>>> from sys import getsizeof
>>> my_comp = [x * 5 for x in range(1000)]
>>> my_gen = (x * 5 for x in range(1000))
>>> getsizeof(my_comp)
9024  
>>> getsizeof(my_gen)
88
複製代碼

生成器一次生成一個項目 - 所以它比列表更有內存效率。

例如,當您想迭代列表時,Python會爲整個列表保留內存。生成器不會將整個序列保留在內存中,而且只會根據須要「生成」序列的下一個元素。

最後的想法

可能會嚇到或勸阻新手程序員的第一件事就是教育材料的規模。這裏的訣竅是將每一個概念視爲語言提供的選項,您不該該同時學習全部語言概念和模塊。

總有不一樣的方法來解決同一個任務。把它做爲完成工做的另外一個工具。

查看英文原文

查看更多文章:www.apexyun.com

公衆號:銀河系1號

聯繫郵箱:public@space-explore.com

(未經贊成,請勿轉載)

相關文章
相關標籤/搜索