如何让 Python 将函数识别为生成器函数?

How to make Python recognize a function as a generator function?

首先,考虑下面的代码(接下来我将讨论subgen()的几个版本):

>>> def maingen(i):
...    print("maingen started")
...    yield from subgen(i)
...    print("maingen finished")
...
>>> for i in maingen(5):
...     print(i)
...     

我想写几个 subgen 生成器函数。

正常的是:

>>> def subgen_1(i):
...     yield i + 1
...     yield i + 2
...     yield i + 3

没有问题,输出符合预期:

maingen started
6
7
8
maingen finished

现在,我想要另一个版本的 subgen ,它不会产生任何结果...

如果我尝试这样做:

>>> def subgen_2(i):
...     i * 3

我有一个例外:

maingen started
Traceback (most recent call last):
  ...
TypeError: 'NoneType' object is not iterable

预计:subgen_2 不是生成器函数。

好的,下一个。我可以在某个地方阅读(比如第一个答案here) 我必须提出 StopIteration。 (请注意,作为新手,我无法评论此答案。)

>>> def subgen_3(i):
...     i * 3
...     raise StopIteration()
...     

PEP 479"Finally, the proposal also clears up the confusion about how to terminate a generator: the proper way is return, not raise StopIteration." 中所述,我只得到:

maingen started

(没有maingen finished...)

我发现让一切顺利的唯一方法是:

>>> def subgen_4(i):
...     i * 3
...     return
...     yield
... 

有了这个,我得到:

maingen started
maingen finished

万岁!但是这个解决方案并不漂亮...

有没有人有更好或更 pythonic 的想法?

能不能写个装饰器偷偷加丑陋的yield语句?

如果您希望子生成器不产生任何结果,那么return一个空迭代器:

def subgen_2(i):
    i * 3
    return iter([]) #empty iterator

你得到 TypeError: 'NoneType' object is not iterable 的原因是因为在你的子生成器中没有 yield 语句它不是生成器,而是一个隐式 returned None.

通过 return 一个不产生任何值的有效迭代器,您将能够 yield from subgen_2() 没有错误并且不会产生任何额外的值。

隐藏它的另一种方法(我真的不明白你为什么要这样做)是制作一个装饰器,它实际上只是调用你的函数然后 return iter(())yield from [].

def gen_nothing(f):
    @functools.wraps(f)
    def wrapper(*args,**kw):
        f(*args,**kw)
        yield from []
    return wrapper

但是这产生的唯一区别是堆栈将需要一个额外的包装帧,这意味着您的 Traceback 消息将有更多噪音:

Traceback (most recent call last):
  File "/Users/Tadhg/Documents/codes/test.py", line 15, in <module>
    for i in subgen():
  File "/Users/Tadhg/Documents/codes/test.py", line 6, in wrapper
    f(*args,**kw)
  File "/Users/Tadhg/Documents/codes/test.py", line 12, in subgen
    3/0
ZeroDivisionError: division by zero

看完所有评论,我觉得更pythonic的方法是重写maingen:

def maingen(i):
    print("maingen started")
    gen = subgen(i)
    if gen:
        yield from gen
    print("maingen finished")

这样,一个 subgen 实际上是一个生成器函数:

def subgen_1(i):
    yield i + 1
    yield i + 2
    yield i + 3

subgen 这是一个简单的函数:

def subgen_2(i):
    i * 3

maingen 生成器函数中 "injected" 时都是 "accepted",不需要一些丑陋的 yield 语句。