Python空生成器函数


97

在python中,可以通过将yield关键字放在函数主体中来轻松定义迭代器函数,例如:

def gen():
    for i in range(100):
        yield i

我如何定义不产生任何值的生成器函数(生成0个值),以下代码不起作用,因为python无法知道它应该是生成器而不是普通函数:

def empty():
    pass

我可以做类似的事情

def empty():
    if False:
        yield None

但这将是非常丑陋的。有什么好的方法可以实现空的迭代器功能?

Answers:


132

您可以return在生成器中使用一次;它会停止迭代而不会产生任何结果,因此提供了一种使函数超出范围的明确选择。因此,用于yield将函数转换为生成器,但在return产生任何内容之前先终止该生成器。

>>> def f():
...     return
...     yield
... 
>>> list(f())
[]

我不确定这是否比您拥有的要好得多-它只是将无操作if语句替换为无操作yield语句。但这更惯用了。请注意,仅使用yield不起作用。

>>> def f():
...     yield
... 
>>> list(f())
[None]

为什么不只是使用iter(())

这个问题专门询问一个空的生成器函数。因此,我将其视为关于Python语法的内部一致性的问题,而不是一般而言有关创建空迭代器的最佳方法的问题。

如果问题实际上是关于创建一个空迭代器的最佳方法,那么您可能同意Zectbumo关于使用它的iter(())替代方法。但是,请务必注意iter(())不要返回函数!它直接返回一个空的Iterable。假设您正在使用一个期望可调用返回一个可迭代对象的API 。您必须执行以下操作:

def empty():
    return iter(())

(信用额应转到Unutbu,以给出此答案的第一个正确版本。)

现在,您可能会发现上面的内容更加清晰,但是我可以想象一下情况不太清楚的情况。考虑以下(伪造的)生成器函数定义列表的示例:

def zeros():
    while True:
        yield 0

def ones():
    while True:
        yield 1

...

在这个长长的列表的末尾,我宁愿看到其中包含yield的内容,如下所示:

def empty():
    return
    yield

或者,在Python 3.3及更高版本中(如DSM所建议的那样):

def empty():
    yield from ()

对存在yield关键字清楚地在最短的一瞥,这只是另一个发生器功能,所有其他的一模一样。花更多的时间才能看到该iter(())版本正在执行相同的操作。

这是一个细微的差异,但是老实说,我认为yield基于功能的函数更具可读性和可维护性。


对于确实if False: yield不知道这种模式的人来说,这确实好于但仍然有些困惑
Konstantin Weitz 2012年

1
w,回来后有东西吗?我期望有类似的东西itertools.empty()
Grault

1
@jesdisciple return意味着在生成器内部有些不同。更像是break
senderle 2014年

我喜欢这种解决方案,因为它(相对)简洁,并且不像相比进行任何额外的工作False
Pi Marillion

Unutbu的答案不是您提到的“真正的生成器函数”,因为它返回一个迭代器。
Zectbumo

72
iter(())

不需要发电机。来吧!


3
我绝对最喜欢这个答案。它是一个快速,易于编写,快速执行的方法,并且对我而言比iter([])一个简单的事实更具吸引力,因为事实()是一个常数,而[]每次调用它可能会在内存中实例化一个新的列表对象。
Mumbleskates

对我来说,这似乎也是最优雅的解决方案。
马提亚斯CM Troffaes,2015年

2
回顾这个线程,我不得不指出,如果您想要真正替代生成器函数,则需要编写类似empty = lambda: iter(())或的东西def empty(): return iter(())
senderle

如果您必须拥有一个发电机,那么您也可以像其他人建议的那样使用(_表示()中的_)
Zectbumo

1
@Zectbumo,那仍然不是生成器函数。它只是一个发电机。生成器函数每次被调用时都会返回一个新生成器。
senderle

50

Python 3.3(因为我yield from踢了,因为@senderle偷走了我的第一个念头):

>>> def f():
...     yield from ()
... 
>>> list(f())
[]

但是我不得不承认,我很难为此提出一个用例,iter([])否则用例(x)range(0)就不能很好地解决问题。


我真的很喜欢这种语法。产量真棒!
康斯坦丁·威茨

2
我认为这对于新手来说比return; yield或更具可读性if False: yield None
2014年

1
这是最优雅的解决方案
Maksym Ganenko

“但是我不得不承认,我很难为此提供一个用例,iter([])或者用例(x)range(0)不能很好地解决。” ->不确定是什么(x)range(0),但用例可以是某些继承类中的完整生成器所覆盖的方法。出于一致性的目的,您甚至希望其他继承者继承的基础生成器也像覆盖它们的生成器一样返回生成器。
VedranŠego'19


4

它一定是生成器函数吗?如果没有的话

def f():
    return iter([])


1

就像@senderle所说的,使用这个:

def empty():
    return
    yield

我写这个答案主要是为了分享另一个理由。

选择此解决方案优先于其他解决方案的一个原因是,对于解释器而言,它是最佳的。

>>> import dis
>>> def empty_yield_from():
...     yield from ()
... 
>>> def empty_iter():
...     return iter(())
... 
>>> def empty_return():
...     return
...     yield
...
>>> def noop():
...     pass
...
>>> dis.dis(empty_yield_from)
  2           0 LOAD_CONST               1 (())
              2 GET_YIELD_FROM_ITER
              4 LOAD_CONST               0 (None)
              6 YIELD_FROM
              8 POP_TOP
             10 LOAD_CONST               0 (None)
             12 RETURN_VALUE
>>> dis.dis(empty_iter)
  2           0 LOAD_GLOBAL              0 (iter)
              2 LOAD_CONST               1 (())
              4 CALL_FUNCTION            1
              6 RETURN_VALUE
>>> dis.dis(empty_return)
  2           0 LOAD_CONST               0 (None)
              2 RETURN_VALUE
>>> dis.dis(noop)
  2           0 LOAD_CONST               0 (None)
              2 RETURN_VALUE

如我们所见,的empty_return字节码与常规的空函数完全相同;其余的执行许多其他操作,这些操作无论如何都不会改变行为。empty_return和之间的唯一区别noop是,前者已设置了generator标志:

>>> dis.show_code(noop)
Name:              noop
Filename:          <stdin>
Argument count:    0
Positional-only arguments: 0
Kw-only arguments: 0
Number of locals:  0
Stack size:        1
Flags:             OPTIMIZED, NEWLOCALS, NOFREE
Constants:
   0: None
>>> dis.show_code(empty_return)
Name:              empty_return
Filename:          <stdin>
Argument count:    0
Positional-only arguments: 0
Kw-only arguments: 0
Number of locals:  0
Stack size:        1
Flags:             OPTIMIZED, NEWLOCALS, GENERATOR, NOFREE
Constants:
   0: None

当然,该论点的强度非常依赖于所使用的Python的特定实现;具体请参见第5章。一个足够聪明的替代解释器可能会注意到其他操作毫无用处,并对其进行了优化。但是,即使存在这种优化,它们也需要解释器花费时间执行优化,并防止优化假设被破坏,例如iter全局范围内的标识符反弹到其他事物(即使这很可能表示错误,如果它实际上发生了)。在empty_return没有什么可以优化的情况下,因此即使是天真的CPython也不会在任何虚假操作上浪费时间。


0
generator = (item for item in [])
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.