Python列表推导(list comprehension)VS 生成器表达式(generator expression)

3,134 阅读6分钟


你知道以下语法之间的区别吗?

[x for x in range(5)]

(x for x in range(5))

tuple(range(5))

本文将向您介绍这里的区别。

关于列表的5个事实

首先,对列表进行简短回顾(在其他编程语言中通常称为“数组”):

列表是一种可以表示为元素集合的数据。一个简单的列表如下所示:[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()函数创建列表。

>>> 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循环创建列表。

使用列表推导来查看前一个示例的实现:

>>> 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

(未经同意,请勿转载)