如何定义一个空生成器函数?

生成器函数可以通过在函数体中放入 yield关键字来定义:

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

如何定义一个空生成器函数?

下面的代码不起作用,因为 Python 不知道它应该是一个生成器函数而不是普通函数:

def empty():
pass

我可以这样做:

def empty():
if False:
yield

但那样会很难看,有更好的办法吗?

55478 次浏览

您可以在生成器中使用 return一次; 它停止迭代而不产生任何结果,因此提供了一个明确的替代方案,以避免函数超出作用域。因此,使用 yield将函数转换为生成器,但在生成任何内容之前使用 return终止生成器。

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

我不确定它是否比你所拥有的更好——它只是用一个不可操作的 yield语句替换了一个不可操作的 if语句。但它更加地道。请注意,仅仅使用 yield是不起作用的。

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

为什么不用 iter(())呢?

这个问题特别询问关于空 发电机功能发电机功能的问题。出于这个原因,我认为这是一个关于 Python 语法内部一致性的问题,而不是一个关于创建空迭代器的最佳方法的问题。

如果问题实际上是关于创建空迭代器的最佳方法,那么您可能同意 Zectbuo使用 iter(())代替。然而,重要的是要注意 iter(())不返回函数!它直接返回一个空的迭代器。假设您正在使用一个 API,该 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的函数更具可读性和可维护性。

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

Python 3.3(因为我正在使用 yield from,而@senderle 偷走了我的第一个想法) :

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

但是我必须承认,我很难想出一个 iter([])或者 (x)range(0)不能同样好用的用例。

必须是生成器函数吗? 如果不是,那么

def f():
return iter(())

另一个选择是:

(_ for _ in ())
iter(())

你不 要求一个发电机。来吧,伙计们!

generator = (item for item in [])

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

@ senderle said一样,使用这个:

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_returnnoop之间的唯一区别是前者设置了生成器标志:

>>> 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标识符被反弹到其他东西上(即使如果它真的发生了,那很可能意味着一个 bug)。在 empty_return的例子中,没有什么需要优化的地方,因此即使是相对幼稚的 CPython 也不会在任何虚假的操作上浪费时间。

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

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


>>> list(EmptyGenerator())
[]

还没有人提到它,但是在没有参数的情况下调用 内置函数 zip会返回一个空迭代器:

>>> it = zip()
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration