130

在python中,可以很容易地定义一个迭代器函数,通过将yield关键字放在函数体中,例如:

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

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

def empty():
    pass

我可以做类似的事情

def empty():
    if False:
        yield None

但这会非常难看。有没有什么好方法来实现一个空的迭代器函数?

4

9 回答 9

167

您可以return在生成器中使用一次;它在不产生任何结果的情况下停止迭代,因此提供了一个明确的替代方案来让函数超出范围。所以使用yield将函数变成一个生成器,但在它之前return终止生成器,然后再产生任何东西。

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

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

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

为什么不直接使用iter(())

这个问题专门询问了一个空的生成器函数。出于这个原因,我认为这是一个关于 Python 语法内部一致性的问题,而不是一个关于创建空迭代器的最佳方法的问题。

如果问题实际上是关于创建空迭代器的最佳方法,那么您可能同意Zectbumo关于使用的iter(())替代方法。但是,重要的是要注意iter(())不返回函数!它直接返回一个空的可迭代对象。假设您正在使用一个 API,该 API 需要一个每次调用时返回一个可迭代对象的可调用对象,就像普通的生成器函数一样。你必须做这样的事情:

def empty():
    return iter(())

(信用应该归功于 Unutbu给出了这个答案的第一个正确版本。)

现在,您可能会发现上面的内容更清楚,但我可以想象它不太清楚的情况。考虑这个一长串(人为的)生成器函数定义的例子:

def zeros():
    while True:
        yield 0

def ones():
    while True:
        yield 1

...

在那长长的列表的最后,我宁愿看到里面有 ayield的东西,像这样:

def empty():
    return
    yield

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

def empty():
    yield from ()

关键字的存在yield一目了然地表明这只是另一个生成器函数,与所有其他函数完全相同。需要更多时间才能看到该iter(())版本正在执行相同的操作。

这是一个微妙的区别,但老实说,我认为yield基于 - 的函数更具可读性和可维护性。

另请参阅user3840170的这个很好的答案,它用于dis说明为什么这种方法更可取的另一个原因:它在编译时发出的指令最少。

于 2012-11-06T03:22:44.500 回答
76
iter(())

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

于 2014-10-09T06:26:25.960 回答
65

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

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

但我不得不承认,我很难想出一个用例来解决这个问题,iter([])或者(x)range(0)不能很好地工作。

于 2012-11-06T03:28:54.490 回答
21

另一种选择是:

(_ for _ in ())
于 2013-06-25T03:52:37.753 回答
8

就像@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是前者设置了生成器标志:

>>> 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 的特定实现。一个足够聪明的替代解释器可能会注意到其他操作毫无用处,并对其进行优化。但是,即使存在这样的优化,它们也需要解释器花时间执行它们并防止优化假设被破坏,例如iter全局范围内的标识符被反弹到其他东西(即使如果它很可能表明存在错误)确实发生了)。在empty_return根本没有什么可优化的情况下,因此即使是相对幼稚的 CPython 也不会在任何虚假操作上浪费时间。

于 2020-04-29T07:29:31.810 回答
3

它必须是生成器函数吗?如果没有,怎么办

def f():
    return iter(())
于 2012-11-06T03:29:00.510 回答
2

制作空迭代器的“标准”方法似乎是 iter([])。我建议将 [] 作为 iter() 的默认参数;这被很好的论据拒绝了,见http://bugs.python.org/issue25215 - Jurjen

于 2015-09-22T19:17:47.423 回答
0
generator = (item for item in [])
于 2015-06-15T20:55:16.833 回答
0

我想举一个基于类的例子,因为我们还没有任何建议。这是一个不生成任何项目的可调用迭代器。我相信这是解决问题的直接且描述性的方法。

class EmptyGenerator:
    def __iter__(self):
        return self
    def __next__(self):
        raise StopIteration

>>> list(EmptyGenerator())
[]
于 2020-09-06T06:09:21.610 回答