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
3
  • It looks likes part of a context manager, since it has a begin() followed by yield followed by end().
    – khelwood
    Commented Aug 13, 2021 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
    Commented Aug 13, 2021 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
    Commented Aug 13, 2021 at 17:50

1 Answer 1

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
1
  • 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. Commented Aug 13, 2021 at 18:29

Not the answer you're looking for? Browse other questions tagged or ask your own question.