Python空生成器函数

Kon*_*itz 86 python generator

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

def gen():
    for i in range(100):
        yield i
Run Code Online (Sandbox Code Playgroud)

如何定义不产生值的生成器函数(生成0值),以下代码不起作用,因为python不能知道它应该是生成器而不是正常函数:

def empty():
    pass
Run Code Online (Sandbox Code Playgroud)

我可以做点什么

def empty():
    if False:
        yield None
Run Code Online (Sandbox Code Playgroud)

但那会非常难看.有没有很好的方法来实现一个空的迭代器函数?

sen*_*rle 114

你可以return在发电机中使用一次; 它会停止迭代而不会产生任何东西,因此提供了一个明确的替代方法,让函数超出范围.因此,使用yield将函数转换为生成器,但在它之前return用于在生成任何内容之前终止生成器.

>>> def f():
...     return
...     yield
... 
>>> list(f())
[]
Run Code Online (Sandbox Code Playgroud)

我不确定它比你拥有的要好得多 - 它只是用无操作if语句取代了无操作yield语句.但它更具惯用性.请注意,只是使用yield不起作用.

>>> def f():
...     yield
... 
>>> list(f())
[None]
Run Code Online (Sandbox Code Playgroud)

为什么不用iter(())

这个问题具体询问空发生器功能.出于这个原因,我认为它是关于Python语法的内部一致性的问题,而不是关于一般创建空迭代器的最佳方法的问题.

如果问题实际上是关于创建空迭代器的最佳方法,那么您可能会同意Zectbumo关于使用它iter(()).但是,重要的是观察iter(())不返回功能!它直接返回一个空的iterable.假设您正在使用期望可返回可迭代的可调用的API .你必须做这样的事情:

def empty():
    return iter(())
Run Code Online (Sandbox Code Playgroud)

(信用证应该转到Unutbu,以提供此答案的第一个正确版本.)

现在,你可能会发现上面更清楚,但我可以想象它不太清楚的情况.考虑这个(人为的)生成器函数定义列表的示例:

def zeros():
    while True:
        yield 0

def ones():
    while True:
        yield 1

...
Run Code Online (Sandbox Code Playgroud)

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

def empty():
    return
    yield
Run Code Online (Sandbox Code Playgroud)

或者,在Python 3.3及以上版本中(如DSM所建议),这个:

def empty():
    yield from ()
Run Code Online (Sandbox Code Playgroud)

yield关键字的存在使得它在最简洁的一瞥中清楚地表明这只是另一个生成器功能,与所有其他功能完全一样.需要花费更多的时间才能看到该iter(())版本正在做同样的事情.

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


Zec*_*umo 63

iter(())
Run Code Online (Sandbox Code Playgroud)

不需要发电机.来吧伙计们!

  • 我绝对喜欢这个答案最好.它快速,易于编写,执行速度快,对于我来说比"iter([])"更吸引人,因为`()`是一个常量,而`[]`可以在内存中实例化一个新的列表对象它被称为时间. (3认同)
  • @Zectbumo,那仍然不是生成器*函数*。它只是一个发电机。生成器函数每次被调用时都会返回一个新的生成器。 (3认同)
  • 回顾这个线程,我不得不指出,如果你想要一个真正的生成器函数的替代品,你应该写一些类似 `empty = lambda: iter(())` 或 `def empty( ): 返回 iter(())`。 (2认同)
  • 这将返回一个“tuple_iterator”而不是“generator”。如果您的生成器不需要返回任何内容,请不要使用此答案。 (2认同)

DSM*_*DSM 46

Python 3.3(因为我很开心yield from,因为@senderle偷走了我的第一个想法):

>>> def f():
...     yield from ()
... 
>>> list(f())
[]
Run Code Online (Sandbox Code Playgroud)

但我不得不承认,我很难想出一个用于此的用例,iter([])或者(x)range(0)不能同样好用.

  • 我认为这对新手来说比 return 更易读;yield` 或 `if False: yield None`。 (2认同)

Ben*_*war 14

另一种选择是:

(_ for _ in ())
Run Code Online (Sandbox Code Playgroud)

  • 我也喜欢这个.我可以建议一个空元组而不是空列表吗?这样你就可以[不断折叠](http://stackoverflow.com/a/1915951/577088). (3认同)
  • `(()中没有任何东西``读得更好恕我直言. (3认同)
  • 与其他选项不同,Pycharm 认为这与生成器使用的标准类型提示一致,例如“Generator[str, Any, None]” (2认同)

edd*_*uld 6

我更喜欢以下内容:

def foo():
  raise StopIteration()
  yield
Run Code Online (Sandbox Code Playgroud)

"yield"将其转换为生成器,而Exception意味着None不包含在结果中(纯粹的空结果).

  • 在工作中有一个PEP用于在RuntimeExceptions通过生成器时将引发的StopIteration异常更改,以避免无保护的迭代器访问中无意中未捕获的异常从静默传递.因此,它不是非常具有前瞻性.用简单的`return`返回你的`raise`语句,或者简单地使用`return iter(())`,是更好的,更适合未来的选择. (5认同)
  • https://www.python.org/dev/peps/pep-0479/ - @eddiewould:期待3.5+.手动提升StopIteration仍然是相当糟糕的做法,无论你使用什么版本,即使它工作得很好. (2认同)

use*_*170 6

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

def empty():
    return
    yield
Run Code Online (Sandbox Code Playgroud)

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

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

>>> 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
Run Code Online (Sandbox Code Playgroud)

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

>>> 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
Run Code Online (Sandbox Code Playgroud)

当然,这个论点的强弱很大程度上取决于所使用的 Python 的特定实现;一个足够聪明的替代解释器可能会注意到其他操作毫无用处并优化它们。然而,即使存在这样的优化,它们也需要解释器花时间来执行它们并防止优化假设被破坏,例如iter全局范围内的标识符被反弹到其他东西(即使这很可能表明存在错误,如果它确实发生过)。在empty_return没有什么可以优化的情况下,即使是相对幼稚的 CPython 也不会在任何虚假操作上浪费时间。