【问题标题】:How to write Python generator function that never yields anything如何编写永远不会产生任何东西的 Python 生成器函数
【发布时间】:2011-09-10 03:04:29
【问题描述】:

我想编写一个从不实际产生任何东西的 Python 生成器函数。基本上它是一个“无所事事”的插件,可以被其他期望调用生成器的代码使用(但并不总是需要它的结果)。到目前为止,我有这个:

def empty_generator():
    # ... do some stuff, but don't yield anything
    if False:
        yield

现在,这行得通,但我想知道是否有一种更具表现力的方式来表达相同的意思,即,将函数声明为生成器,即使它永远不会产生任何值。我在上面使用的技巧是在我的函数中向 Python 显示一个 yield 语句,即使它无法访问。

【问题讨论】:

  • 出于好奇,为什么它必须是生成器?我无法想象哪种正确的调用者代码可以明确地需要生成器......
  • @static_rtti 我刚刚发现假生成器非常适合延迟加载,具体取决于上下文。
  • @Ekevoo 你能链接到一个例子吗?
  • @static_rtti 这里。我确信有更好的方法来做我所做的事情,但它完成了工作。 github.com/ekevoo/hfbr/blob/…
  • 作为记录,我会将if False: yield 放在函数的顶部,以便立即清楚你是什么正在做。这实际上是它相对于return; yield 解决方案的一个优势:无论哪种方式,这个构造都充当了声明“我希望这是一个生成器”的注释,我们通常将影响整个函数的注释放在顶部。原因 - 它有助于引导读者。

标签: python generator yield


【解决方案1】:

为了获得最大的可读性和可维护性,我会优先考虑位于函数顶部的构造。所以要么

  1. 你原来的 if False: yield 构造,但被提升到第一行,或者
  2. 一个单独的装饰器,它将生成器行为添加到非生成器可调用对象中。

(假设你不只是需要一个可调用的,它做了一些事情然后返回一个空的迭代器/迭代器。如果是这样,那么你可以只使用一个常规函数,最后使用return ()/return iter(())。)

想象一下你的代码的读者会看到:

def name_fitting_what_the_function_does():
    # We need this function to be an empty generator:
    if False: yield

    # that crucial stuff that this function exists to do

将 this 置于顶部会立即向该函数的每个读者提示该细节,会影响整个函数 - 会影响对该函数的行为和使用的预期和解释。

你的函数体有多长?不止几行?那么作为一个读者,如果我直到最后都没有得到这个函数是生成器的提示,我会对作者感到义愤填膺和谴责,因为我可能会花费大量的精神成本在我的脑海中编织一个基于模型的模型假设这是一个常规函数 - 理想情况下,生成器中的第一个 yield 应该立即可见,当您甚至不知道要查找它时

另外,在一个多于几行的函数中,函数开头的构造更值得信赖 - 我可以相信任何看过函数的人都可能第一次看到它每次他们看它时都排成一行。这意味着如果这条线错误或损坏,有人会发现它的可能性更高。这意味着我可以不那么警惕整个东西实际上已经损坏但被以一种使损坏不明显的方式使用的可能性。

如果您正在与非常熟悉 Python 工作原理的人一起工作,您甚至可以忽略该评论,因为对于立即记得 yield 是 Python 将函数变成生成器的原因的人来说,很明显,这是效果,也可能是意图,因为没有其他理由让正确的代码有一个未执行的yield

或者,你可以走装饰路线:

@generator_that_yields_nothing
def name_fitting_what_the_function_does():
    # that crucial stuff for which this exists


def generator_that_yields_nothing(wrapped):
    @functools.wraps(wrapped)
    def wrapper_generator():
        if False: yield
        wrapped()
    return wrapper_generator

【讨论】:

  • 十年过去了,但我很欣赏这个深思熟虑的答案!
【解决方案2】:

更短的解决方案:

def empty_generator():
  yield from []

【讨论】:

  • 有趣。不过对于 Python 3。
  • 我认为最优雅和语法检查器友好
  • 如果你想帮助 Python 避免创建空列表的开销,建议使用元组文字 () 而不是列表文字 [](取决于 Python 实现的“智能”程度,它可能能够识别出你从不使用列表,并相应地优化到它的等价点,但 () 只是由语言保证不可变,这是正常的和预期的 [] is not []() is (),等等与让[] 几乎免费相比,Python 实现使() 有效地免费所需的聪明才智和努力要少得多)。
【解决方案3】:

另一种方法是

def empty_generator():
    return
    yield

并不是真的“更具表现力”,而是更短。 :)

请注意,iter([]) 或简单的 [] 也可以。

【讨论】:

  • 我的投票投给了 iter([])。 [] 不是真正的迭代器(你不能在它上面调用 .next() 等等)
  • iter([]) 在我的情况下不起作用,因为我实际上在函数的顶部做了一些工作(在我的原始帖子中没有显示,对此感到抱歉)。我喜欢先收益后收益的解决方案。
  • @John:一定要添加评论来解释它在做什么! :)
  • 这很奇怪,因为通常 returnyield 不能一起出现在函数定义代码中。但我注意到,如果将 return 替换为 return 25,则会引发语法错误:'return' with argument inside generator。所以 return with 和 return without argument 是有区别的。这实际上有什么区别?
  • @eyquem: @eyquem: return 在生成器中与到达代码末尾的流程相同——它将使生成器丢弃它的框架并引发StopIteration。返回值是没有意义的,就像流到代码末尾没有返回值一样。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-04
  • 1970-01-01
  • 2016-04-27
  • 2012-06-19
  • 1970-01-01
相关资源
最近更新 更多