Python空生成器函数

时间:2012-11-06 03:08:14

标签: python generator

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

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

如何定义不产生值的生成器函数(生成0值),以下代码不起作用,因为python不能知道它应该是生成器而不是普通函数:

def empty():
    pass

我可以做类似

的事情
def empty():
    if False:
        yield None

但那会非常难看。有没有很好的方法来实现一个空的迭代器函数?

11 个答案:

答案 0 :(得分:101)

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

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

我不确定它比你拥有的要好得多 - 它只是用no-op if语句替换了no-op yield语句。但它更具惯用性。请注意,仅使用yield不起作用。

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

为什么不使用iter(())

此问题具体询问空生成器函数。出于这个原因,我认为它是关于Python语法内部一致性的问题,而不是关于一般创建空迭代器的最佳方法的问题。

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

答案 1 :(得分:61)

iter(())

您不需要生成器。来吧!

答案 2 :(得分:43)

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

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

但我不得不承认,我很难想出一个用例,因为iter([])(x)range(0)不会同样有效。

答案 3 :(得分:12)

另一种选择是:

(_ for _ in ())

答案 4 :(得分:6)

我更喜欢以下内容:

def foo():
  raise StopIteration()
  yield

"产量"将它变成生成器,而异常意味着结果中不包含任何内容(纯粹的空结果)。

答案 5 :(得分:4)

它必须是发电机功能吗?如果没有,那怎么样

def f():
    return iter([])

答案 6 :(得分:3)

制作空迭代器的“标准”方法似乎是iter([])。 我建议将[]设为默认参数iter();这被驳回了很好的论据,请参阅http://bugs.python.org/issue25215 - Jurjen

答案 7 :(得分:3)

就像@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_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标识符反弹到其他事物(即使这很可能会指出一个错误(如果它确实发生了)。在empty_return的情况下,根本没有什么值得优化的,因此,即使是相对天真的CPython也不会在任何虚假操作上浪费时间。

答案 8 :(得分:0)

generator = (item for item in [])

答案 9 :(得分:0)

对于那些真正需要一个功能而且实际上需要一个发电机的人

empty = lambda: (_ for _ in ())

答案 10 :(得分:0)

由于我们还没有任何建议,因此我想举一个基于类的示例。这是一个不生成任何项目的可调用迭代器。我相信这是解决问题的一种简单明了的方式。

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

>>> list(EmptyGenerator())
[]