Python空生成器函数

Python空生成器函数,python,generator,Python,Generator,在python中,通过将yield关键字放在函数体中,可以轻松定义迭代器函数,例如: def gen(): for i in range(100): yield i 如何定义一个不产生值(生成0个值)的生成器函数,以下代码不起作用,因为python无法知道它应该是生成器而不是普通函数: def empty(): pass 我可以做类似的事情 def empty(): if False: yield None 但那将是非常丑陋的。有什么

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

def gen():
    for i in range(100):
        yield i
如何定义一个不产生值(生成0个值)的生成器函数,以下代码不起作用,因为python无法知道它应该是生成器而不是普通函数:

def empty():
    pass
我可以做类似的事情

def empty():
    if False:
        yield None

但那将是非常丑陋的。有什么好方法可以实现空迭代器函数吗?

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

>>> def f():
...     return
...     yield
... 
>>> list(f())
[]
我不确定它是否比你现在拥有的要好得多——它只是用一个no-op
yield
语句替换了一个no-op
if
语句。但它更地道。请注意,仅使用
yield
不起作用

>>> def f():
...     yield
... 
>>> list(f())
[None]
为什么不直接使用iter(())? 此问题专门询问空生成器函数。因此,我认为这是一个关于Python语法内部一致性的问题,而不是一个关于创建空迭代器的最佳方法的问题

如果问题实际上是关于创建空迭代器的最佳方法,那么您可能同意使用
iter(())
。然而,重要的是要注意,
iter(())
不返回函数!它直接返回一个空的iterable。假设您正在使用一个API,该API需要一个可调用函数,该函数每次调用时都返回一个iterable,就像普通的生成器函数一样。您必须执行以下操作:

def empty():
    return iter(())
def empty():
    return
    yield
(给出此答案的第一个正确版本应归功于。)

现在,你可能会发现上面的内容更清晰,但我可以想象在什么情况下,它会变得不那么清晰。考虑一个长的(设计的)生成器函数定义的例子:

def zeros():
    while True:
        yield 0

def ones():
    while True:
        yield 1

...
在这个长长的列表的末尾,我更希望看到一些带有
收益率的东西,比如:

def empty():
    return iter(())
def empty():
    return
    yield
或者,在Python3.3及更高版本中(如所建议),这是:

yield
关键字的出现,一眼就能清楚地看出,这只是另一个生成器函数,与所有其他函数完全相同。需要更多的时间才能看到
iter(())
版本也在做同样的事情

这是一个微妙的区别,但我诚实地认为基于
的函数更可读和可维护

另请参见使用
dis
来说明此方法更可取的另一个原因的伟大答案:它在编译时发出的指令最少。

Python3.3(因为我从
kick中获得了
收益,而且@senderle偷走了我的第一个想法):


但我必须承认,我很难想出一个这样的用例,对于这个用例,
iter([])
(x)range(0)
不能同样好地工作。

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

def f():
    return iter(())
另一个选择是:

(_ for _ in ())
你不需要发电机。来吧,伙计们

生成空迭代器的“标准”方法似乎是iter([])。
generator = (item for item in [])
我建议将[]作为iter()的默认参数;这被驳回,理由充分,请参见
-Jurjen

就像@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\u return
具有与常规空函数完全相同的字节码;其余的执行一些其他操作,但无论如何都不会改变行为。
empty\u 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
显示代码(noop) 姓名:noop 文件名: 参数计数:0 仅位置参数:0 仅限Kw参数:0 本地人数:0 堆栈大小:1 标志:优化、新本地、无自由 常数: 0:无 >>>显示显示代码(空返回) 名称:空返回 文件名: 参数计数:0 仅位置参数:0 仅限Kw参数:0 本地人数:0 堆栈大小:1 标志:优化、新局部变量、生成器、NOFREE 常数: 0:无

当然,这个论点的力度很大程度上取决于所使用的Python的具体实现;一个足够聪明的替代口译员可能会注意到其他操作毫无用处,并对其进行优化。然而,即使存在这样的优化,也需要解释器花时间执行优化,并防止优化假设被破坏,比如全球范围内的
iter
标识符被反弹到其他东西上(即使这很可能表明实际发生了错误)。在
empty\u return
的情况下,没有什么可以优化的,因此即使是相对幼稚的CPython也不会在任何虚假操作上浪费时间。

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

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

>>> list(EmptyGenerator())
[]

这确实比
if False:yield
好,但是对于不知道这个模式的人来说还是有点困惑,新的,返回后的东西?我期望类似于
itertools.empty()
@jessimport的东西,
return
在生成器中表示不同的东西。它更像是
break
。我喜欢这个解决方案,因为它(相对)简洁,而且它不做任何额外的工作,比如与
False
进行比较。Unutbu的答案不是“真正的生成器f”
class EmptyGenerator:
    def __iter__(self):
        return self
    def __next__(self):
        raise StopIteration

>>> list(EmptyGenerator())
[]