Python空生成器函数
在python中,通过将yield关键字放在函数体中,可以轻松定义迭代器函数,例如: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 但那将是非常丑陋的。有什么
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-opyield
语句替换了一个no-opif
语句。但它更地道。请注意,仅使用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())
[]