生成器推导式是如何工作的?

140
生成器推导式是什么?它是如何工作的?我找不到关于它的教程。

6
明确一点,这里的语言名称是生成器表达式,而不是生成器推导式 - ShadowRanger
5
在2018年7月的Python-dev邮件列表中,讨论了“命名推导语法”的问题。有初步但相当一致的共识认为为了保持一致性,应该称它们为“生成器推导式”。 - Russia Must Remove Putin
8个回答

202

你是否理解列表推导式?如果是这样,那么生成器表达式就像一个列表推导式,但不是找到所有感兴趣的项并将它们打包成列表,而是等待,并逐一从表达式中生成每个项。

>>> my_list = [1, 3, 5, 9, 2, 6]
>>> filtered_list = [item for item in my_list if item > 3]
>>> print(filtered_list)
[5, 9, 6]
>>> len(filtered_list)
3
>>> # compare to generator expression
... 
>>> filtered_gen = (item for item in my_list if item > 3)
>>> print(filtered_gen)  # notice it's a generator object
<generator object <genexpr> at 0x7f2ad75f89e0>
>>> len(filtered_gen) # So technically, it has no length
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'generator' has no len()
>>> # We extract each item out individually. We'll do it manually first.
... 
>>> next(filtered_gen)
5
>>> next(filtered_gen)
9
>>> next(filtered_gen)
6
>>> next(filtered_gen) # Should be all out of items and give an error
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> # Yup, the generator is spent. No values for you!
... 
>>> # Let's prove it gives the same results as our list comprehension
... 
>>> filtered_gen = (item for item in my_list if item > 3)
>>> gen_to_list = list(filtered_gen)
>>> print(gen_to_list)
[5, 9, 6]
>>> filtered_list == gen_to_list
True
>>> 

由于生成器表达式一次只需产生一个项目,因此它可以大大节省内存使用。生成器表达式在需要逐个取出一个项目,基于该项目进行大量计算,然后转到下一个项目的情况下最有意义。如果您需要多个值,则也可以使用生成器表达式并一次取几个值。如果程序在继续之前需要所有值,请改用列表理解。


4
有一个问题。我在Python 3中使用next(gen_name)获取了结果,它有效了。是否有特定的场景需要使用__next__()? - Ankit Vashistha
5
在Python 3中,始终使用next(...)而不是.__next__() - Todd Sewell
2
如果你需要多个值,你也可以使用生成器表达式并一次获取几个。你能否举个例子说明这种用法?谢谢。 - Khanh Le
1
@KhanhLe 你可以做一些简单的事情,比如 first = next(gen)second = next(gen) 来获取多个值以供使用。还有一个很棒的 more-itertools 包,它也有很多方便的工具,比如 chunked - CrazyChucky

37

生成器推导式是列表推导式的延迟版本。

它与列表推导式类似,但返回一个迭代器而不是列表,即一个具有next()方法的对象,该方法会产生下一个元素。

如果您对列表推导式不熟悉,请参见此处,如果想了解生成器,请看这里


5
“生成器推导式是列表推导式的惰性版本”可能是我读过的最好的一句话解释。谢谢! - ruslaniv
1
太好了!我很高兴你觉得有帮助!:) - rz.

7

List/generator comprehension是一种构造方法,您可以使用它从现有列表/生成器创建一个新的列表/生成器。

假设你想要生成1到10每个数字的平方的列表。在Python中,您可以这样做:

>>> [x**2 for x in range(1,11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

在这里,range(1,11) 生成列表 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],但是在 Python 3.0 之前,range 函数不是生成器,因此我使用的是列表推导式。

如果我想创建一个执行相同操作的生成器,可以像这样实现:

>>> (x**2 for x in xrange(1,11))
<generator object at 0x7f0a79273488>

然而在Python 3中,range是一个生成器,因此其结果仅取决于您使用的语法(方括号或圆括号)。


5
这是错误的。外部表达式是否为生成器与内部表达式是否为生成器无关。虽然显然,生成器表达式从列表中获取元素通常没有太大意义,但你可以这样做。 - Antimony
这个能不能重述得更清楚些?我明白你的意思,但正如Antimony所说,它看起来像是你在说另一件事情。(而那看起来是错误的) - Frames Catherine White

5

生成器推导式是创建具有特定结构的生成器的简单方法。假设您想要一个generator,它逐个输出your_list中的所有偶数。如果您使用函数样式创建它,则会像这样:

def allEvens( L ):
    for number in L:
        if number % 2 is 0:
            yield number

evens = allEvens( yourList )

你可以通过生成器推导表达式实现同样的结果:
evens = ( number for number in your_list if number % 2 == 0 )

无论哪种情况,当您调用next(evens)时,您将获得your_list中下一个偶数。

2

生成器表达式的另一个例子:

print 'Generator comprehensions'

def sq_num(n):
    for num in (x**2 for x in range(n)):    
        yield num

for x in sq_num(10):
    print x 

2

生成器推导式是创建可迭代对象的一种方法,类似于在资源上移动的光标。如果您了解mysql游标或mongodb游标,您可能会意识到整个实际数据不会一次性加载到内存中,而是逐一加载一个。您的光标来回移动,但内存中总是有一个行/列表元素。

简而言之,通过使用生成器推导式,您可以轻松地在Python中创建游标。


0
我们可以将其理解为列表推导式的生成器版本。在列表推导式的情况下,我们使用一行代码或短代码创建一个列表,而对于生成器推导式,我们使用一行代码或小代码来创建生成器。 它们具有相同的语法,只需用()(花括号)替换[](方括号)。
generator_composition_object = (num**3 for num in range(5))
print(generator_composition_object)

这将给出类型为生成器的对象的地址。我们还可以在其中使用next()等功能。


0

生成器与列表相同,唯一的区别在于,在列表中,我们一次性获取列表的所有所需数字或项目,但在生成器中,所需数字逐个产生。因此,为了获取所需的项目,我们必须使用for循环来获取所有所需的项目。

#to get all the even numbers in given range
 
def allevens(n):
    for x in range(2,n):
        if x%2==0:
            yield x

for x in allevens(10)
print(x)

#output
2
4
6
8

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接