一个生成器函数可以通过在函数体中放置yield
关键字来定义:
def gen():
for i in range(10):
yield i
如何定义一个空的生成器函数?
以下代码无法工作,因为Python不能知道它应该是一个生成器函数而不是普通函数:
def empty():
pass
我可以做这样的事情:
def empty():
if False:
yield
但是那样会很丑陋,有没有更好的方法呢?
return
,它会停止迭代但不产生任何值,因此提供了一个明确的替代方案来避免函数超出范围。因此,请使用yield
将函数转换为生成器,但在产生任何值之前加上return
以终止生成器。>>> def f():
... return
... yield
...
>>> list(f())
[]
yield
语句替换了一个无操作的if
语句。但它更符合惯用法。请注意,仅使用yield
是行不通的。>>> def f():
... yield
...
>>> list(f())
[None]
iter(())
?这个问题特别在询问一个空的生成器函数。因此,我认为这是关于Python语法内部一致性的问题,而不是关于一般情况下创建空迭代器的最佳方式。
如果问题实际上是关于创建空迭代器的最佳方式,那么你可能会同意Zectbumo关于使用iter(())
的观点。然而,重要的是要注意iter(())
并没有返回一个函数!它直接返回一个空的可迭代对象。假设你正在使用一个期望每次调用时返回一个可迭代对象的可调用API,就像普通的生成器函数一样。你需要像这样做:
def empty():
return iter(())
def zeros():
while True:
yield 0
def ones():
while True:
yield 1
...
yield
的东西,就像这样:def empty():
return
yield
def empty():
yield from ()
有了yield
关键字,仅仅一眼就能清楚地看出这只是另一个生成器函数,与所有其他函数完全相同。需要更多时间才能看出iter(())
版本正在执行相同的操作。
这是微妙的差别,但我真诚地认为基于yield
的函数更易读和可维护。
另请参见user3840170的这个很棒的答案,它使用dis
来显示为什么这种方法更可取:编译时发出最少的指令。
iter(())
你不需要一个发电机。加油伙计们!
iter([])
,更吸引我的是()
是一个常量,而每次调用[]
时可能会在内存中实例化一个新的列表对象。 - Mumbleskatesempty = lambda: iter(())
或者 def empty(): return iter(())
的代码。 - senderletuple_iterator
而不是一个 generator
。如果你的生成器需要返回空值,不要使用这个答案。 - user3064538我选择 Python 3.3 (因为我对 yield from
感兴趣,而且 @senderle 抢走了我的第一个想法):
>>> def f():
... yield from ()
...
>>> list(f())
[]
但我不得不承认,我很难想出一个使用案例来证明 iter([])
或者 (x)range(0)
无法同样有效地发挥作用。
return; yield
或者if False: yield None
更易懂。 - abarnertiter([])
或(x)range(0)
同样适用。不确定(x)range(0)
是什么,但一个使用案例可以是一个方法,该方法旨在被一些继承类中的完整生成器覆盖。为了一致性,您希望即使是其他人继承的基本方法也返回生成器,就像覆盖它的那些方法一样。 - Vedran Šego另一个选项是:
(_ for _ in ())
Generator[str, Any, None]
。 - Michał Jabłoński像@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
上述反汇编在CPython 3.11已经过时,但empty_return
仍然是最优的,与一个空操作函数相比只多了两个操作码(四个字节):
>>> dis.dis(empty_yield_from)
1 0 RETURN_GENERATOR
2 POP_TOP
4 RESUME 0
2 6 LOAD_CONST 1 (())
8 GET_YIELD_FROM_ITER
10 LOAD_CONST 0 (None)
>> 12 SEND 3 (to 20)
14 YIELD_VALUE
16 RESUME 2
18 JUMP_BACKWARD_NO_INTERRUPT 4 (to 12)
>> 20 POP_TOP
22 LOAD_CONST 0 (None)
24 RETURN_VALUE
>>> dis.dis(empty_iter)
1 0 RESUME 0
2 2 LOAD_GLOBAL 1 (NULL + iter)
14 LOAD_CONST 1 (())
16 PRECALL 1
20 CALL 1
30 RETURN_VALUE
>>> dis.dis(empty_return)
1 0 RETURN_GENERATOR
2 POP_TOP
4 RESUME 0
2 6 LOAD_CONST 0 (None)
8 RETURN_VALUE
>>> dis.dis(noop)
1 0 RESUME 0
2 2 LOAD_CONST 0 (None)
4 RETURN_VALUE
当然,这个论点的强度非常依赖于Python在使用中的具体实现;一个足够聪明的替代解释器可能会注意到其他操作都没有用,并将它们优化掉。但是,即使存在这样的优化,也需要解释器花时间执行它们并保护免受优化假设的破坏,比如全局作用域中的iter标识符被重新绑定到其他东西(即使这实际上很可能表示一个错误) 。对于empty_return来说,根本没有什么可以优化的,因为在return语句后,字节码生成就停止了,所以即使是相对幼稚的CPython也不会浪费时间在任何虚假操作上。def empty():
return iter(())
empty = ().__iter__
partial(iter, ())
(from functools import partial
) 或 lambda: iter(())
可能更可取。 - mtraceur我想给出一个基于类的例子,因为我们还没有提出任何建议。 这是一个可调用的迭代器,不生成任何项。 我相信这是解决问题的一种简单明了的描述性方法。
class EmptyGenerator:
def __iter__(self):
return self
def __next__(self):
raise StopIteration
>>> list(EmptyGenerator())
[]
创建一个空迭代器的“标准”方法似乎是iter([])。 我曾建议将[]作为iter()的默认参数;但这个建议被拒绝了,原因充分,详情请见http://bugs.python.org/issue25215 - Jurjen
还没有人提到,但是调用没有参数的内置函数zip
会返回一个空迭代器:
>>> it = zip()
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
generator = (item for item in [])
itertools.empty()
这样的东西。 - Grault#pylint: disable = unreachable
。 - OrenIshShalom