3

I've inherited some fairly buggy code from another project. One of the functions is a callback(draw_ui method) from a library that has a yield statement in it. I'm wondering what the purpose of having a yield in python is if your not using it in a iterator context to return a value. What possible benefit could it have?

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
  • It looks likes part of a context manager, since it has a `begin()` followed by `yield` followed by `end()`. – khelwood Aug 13 '21 at 15:37
  • As a general case, you could use ```yield``` to divide the work of a function in parts, returning an iterator you don't use, and force the execution to continue with ```next()```. This would allow you to clean up whatever the function did when it completes. It's a pretty clumsy approach but it may be what was intended. – sj95126 Aug 13 '21 at 15:49
  • The presence of `yield` makes this a generator function, which returns an instance of `generator` (which is an iterator) when called. The question is, what is that iterator used for? – chepner Aug 13 '21 at 17:50

1 Answers1

1

When a function has empty yield statement, the function will just return None for the first iteration, so you can say that the function acts as generator that can be iterated only once and yields None value:

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

That's what an empty yield does. But when a function has empty yield in between two block of code, it will execute the codes before yield for the first iteration, and the codes after yield will be executed on second iteration:

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

So, it somehow allows you to pause the execution of a function in the middle, however, it throws StopIteration Exception for the second iteration because the function doesn't actually yield anything on the second iteration, to avoid this, you can pass a default value to next function:

Looking at your code, your function is also doing the same thing

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()  # 

So, while calling funciton draw_ui, if control goes to else block, then line outside the else block, i.e. imgui.end() is not called until the second iteration.

This type of implementation is generally done to be used in ContextManager and you can relate to following code snippet copied from contextlib.contextmanager documentation

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
ThePyGuy
  • 17,779
  • 5
  • 18
  • 45
  • Thanks that makes a lot of sense. So this would be a bad design because that "imgui.end()" needs to get called. If the caller doesn't make the second call for some reason then the imgui.end would never get called leading to an exception. Also if there were two draw_ui methods then the one might be called before the second call which would lead to a mismatched imgui begin/end. – Jennifer Heckman Aug 13 '21 at 18:29