DEV Community

loading...

Discussion on: Dead Simple Python: Generators and Coroutines

Collapse
rhymes profile image
rhymes

Great article Jason!

Just a couple of details:

An exception can be raised at the current yield with foo.raise(). -> with foo.throw().

In sorted(self.letters.items(), key=lambda kv: kv[1]) the lambda can be replaced with operator.itemgetter(1), it's one of my favorite small things that are in the standard library :D

I was wondering if there was a way to simplify the coroutine code, using a context manager. The __enter__ could call send(None) and the __exit__ could call close().

With a simple generator is easy to do something similar:

>>> from contextlib import contextmanager
>>> @contextmanager
... def generator():
...     try:
...             yield list(range(10))
...     finally:
...             print("cleanup...")
...
>>> with generator() as numbers:
...     print(numbers)
...
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
cleanup...

But the same doesn't work for a coroutine...

As a first I came up with this:

from contextlib import closing

def print_char():
    try:
        while True:
            print(f"char: {yield}")
    finally:
        print("cleanup...")

with closing(print_char()) as printer:
    printer.send(None)
    for c in "hello world":
        printer.send(c)

>>>
char: h
char: e
char: l
char: l
char: o
char:
char: w
char: o
char: r
char: l
char: d
cleanup...

I came up with something like this then:

from contextlib import ContextDecorator

class coroutine(ContextDecorator):
    def __init__(self, function):
        self.coro = function()

    def __enter__(self):
        self.coro.send(None)

    def __exit__(self, exc_type, exc, exc_tb):
        self.coro.close()

    def send(self, *args):
        self.coro.send(*args)



def print_char():
    while True:
        print(f"char: {yield}")

printer = coroutine(print_char)
with printer:
    for c in "hello world":
        printer.send(c)

but I'm not sure it's improving much :D

Collapse
codemouse92 profile image
Jason C. McDonald Author

Ooh! Thanks for catching that typo! That would have been confusing.

As to the lambda or itemgetter(), I'd actually gone back and forth between the two in writing that example. I think using the lambda there is my own personal preference more than anything.

That is certainly a clever combination of a context and a coroutine, by the way. (Naturally, I didn't discuss contexts in this article, as I haven't discussed them yet in the series.)

Thanks for the feedback.