Search code examples
pythonpython-3.xgeneratorcoroutinestopiteration

Python: Coroutines - StopIteration exception


I am trying to feed a dictionary by using .send(). And my code snippet is below

def coroutine(func):
    def start(*args, **kwargs):
        cr = func(*args, **kwargs)
        next(cr)
        return cr
    return start

@coroutine
def putd(di):

    print("via coroutines adding a key : value to dictionary")

    try:
        item = yield
        for key, value in item.items():
            if key in di:
                print("Key : {0} already exists".format(key))
            else:
                di[key] = value
        print(di)
    except StopIteration :
        print("yield frame got closed")


di = {}
gobj = putd(di)
gobj.send({"plan" : "shuttle"})
gobj.close()

And I believe I am handling the exception properly but still I am getting StopIteration exception.

scratch.py
Traceback (most recent call last):
via coroutines adding a key : value to dictionary
{'plan': 'shuttle'}
File "scratch.py", line 39, in <module>
    gobj.send({"plan" : "shuttle"})
StopIteration

Process finished with exit code 1

Am I not handling that exception properly or am I missing something ? ANy help greatly appreciated.


Solution

  • Your coroutine exits after first send/yield. That generates a StopIteration and you cannot handle it in the coroutine itself, but only when invoking send. From the docs:

    The send() method returns the next value yielded by the generator, or raises StopIteration if the generator exits without yielding another value.

    @coroutine
    def putd(di):
    
        print("via coroutines adding a key : value to dictionary")
    
        try:
            item = yield
            for key, value in item.items():
                if key in di:
                    print("Key : {0} already exists".format(key))
                else:
                    di[key] = value
            print(di)
        except StopIteration :
            print("yield frame got closed")
        # here is an implicit  return None  which terminates the coroutine
    

    I guess you want to keep the coroutine alive accepting as many sends as you want until an explicit close:

    @coroutine
    def putd(di):
    
        print("via coroutines adding a key : value to dictionary")
    
        try:
            while True:
                item = yield
                for key, value in item.items():
                    if key in di: 
                        print("Key : {0} already exists".format(key))
                    else:
                        di[key] = value
                print(di)
        except GeneratorExit:
            print("yield frame got closed")
    

    Please note that now the GeneratorExit exception is caught.