【问题标题】:Purpose of python yield when not used in iterator未在迭代器中使用时 python yield 的目的
【发布时间】:2021-10-16 20:39:56
【问题描述】:

我从另一个项目中继承了一些相当有缺陷的代码。其中一个函数是来自库的回调(draw_ui 方法),其中包含一个 yield 语句。我想知道如果您不在迭代器上下文中使用它来返回值,那么在 python 中使用 yield 的目的是什么。它有什么可能的好处?

def draw_ui(self, graphics):
        self._reset_components()
        imgui.set_next_window_size(200, 200, imgui.ONCE)
        if imgui.begin("Entity"):
            if not self._selected:
                imgui.text("No entity selected")
            else:
                imgui.text(self._selected.name)
                yield
            imgui.end()  # end entity window

【问题讨论】:

  • 它看起来像是上下文管理器的一部分,因为它有一个begin(),后跟yield,后跟end()
  • 一般情况下,您可以使用yield 将函数的工作分成几部分,返回一个您不使用的迭代器,然后使用next() 强制执行继续。这将允许您清理函数完成时所做的任何事情。这是一个相当笨拙的方法,但它可能是预期的。
  • yield 的存在使它成为一个生成器函数,它在调用时返回一个generator 的实例(它是一个迭代器)。问题是,那个迭代器是干什么用的?

标签: python yield imgui


【解决方案1】:

当函数有空的yield 语句时,该函数将只返回None 进行第一次迭代,因此您可以说该函数充当只能迭代一次并产生None 值的生成器:

def foo():
    yield
>>> f = foo()
>>> print(next(f))
None
>>> print(next(f))
Traceback (most recent call last):
  File "<input>", line 1, in <module>
StopIteration

这就是空的yield 所做的。但是当一个函数在两个代码块之间有空的yield时,它会在第一次迭代中执行yield之前的代码,在第二次迭代中执行yield之后的代码:

def foo():
    print('--statement before yield--')
    yield
    print('--statement after yield--')
>>> f = foo()
>>> next(f)
--statement before yield--
>>> next(f)
--statement after yield--
Traceback (most recent call last):
  File "<input>", line 1, in <module>
StopIteration

因此,它以某种方式允许您在中间暂停函数的执行,但是,它会在第二次迭代中抛出 StopIteration 异常,因为该函数在第二次迭代中实际上并没有 yield 任何东西,以避免这样,您可以将默认值传递给next 函数:

看看你的代码,你的函数也在做同样的事情

def draw_ui(self, graphics):
        self._reset_components()
        imgui.set_next_window_size(200, 200, imgui.ONCE)
        if imgui.begin("Entity"):
            if not self._selected:
                imgui.text("No entity selected")
            else:
                imgui.text(self._selected.name)
                yield  #<--------------
            imgui.end()  # 

因此,在调用函数 draw_ui 时,如果控制转到 else 块,则在 else 块之外,即 imgui.end() 直到第二次迭代才会调用。

这种类型的实现一般是在ContextManager中使用的,可以参考下面从contextlib.contextmanager documentation复制的代码sn-p

from contextlib import contextmanager

@contextmanager
def managed_resource(*args, **kwds):
    # Code to acquire resource, e.g.:
    resource = acquire_resource(*args, **kwds)
    try:
        yield resource
    finally:
        # Code to release resource, e.g.:
        release_resource(resource)

>>> with managed_resource(timeout=3600) as resource:
...     # Resource is released at the end of this block,
...     # even if code in the block raises an exception

【讨论】:

  • 谢谢,这很有意义。所以这将是一个糟糕的设计,因为需要调用“imgui.end()”。如果调用者由于某种原因没有进行第二次调用,那么 imgui.end 将永远不会被调用,从而导致异常。此外,如果有两个 draw_ui 方法,则可能会在第二个调用之前调用一个,这将导致 imgui begin/end 不匹配。
猜你喜欢
  • 2010-11-08
  • 2016-05-22
  • 2011-08-23
  • 1970-01-01
  • 1970-01-01
  • 2010-09-20
  • 1970-01-01
  • 2013-01-19
  • 2023-03-14
相关资源
最近更新 更多