I've been preparing for an intermediate Python talk I'm hoping to give in a few months' time, and along the way I've been discovering some new mini-design-patterns that look like they might come in extremely handy!
A lot of them are pretty short, and I've adapted most of them from existing code to my own style and preferences. A lot of the existing code has been in Python 2, and I'm aware that in the transition I might have missed some obvious Better Ways of doing things. (Great example: I've been using my own memoize
decorator for ages without even knowing about the far superior functools.lru_cache
.)
First up is the simple use of Coroutines in Python. I've been aware of them before, but some new examples made all the difference in giving me a concrete use case.
Inspired by this StackOverflow answer, I wrote a restaurant-themed coroutines example here. One advantage of using coroutines this way is that it's extremely easy to "swap out" elements of the system: for instance, I could easily add a Kitchen
whose cookbook has all the wrong types for foods and pass it to run_restaurant
for a less perfect experience; or indeed one which prepares food in half the time of the original!
The code is pretty simple, but the only vaguely scary-looking bit is in utils.py
(standard, amirite?). It's a decorator called coroutine
:
It actually accomplishes something pretty simple: a common annoyance is having to call next
on any coroutine before it advances to the first yield
, i.e. before you can .send
values to it. This decorator advances its decorated generator functions automatically, so we can assume it's starting right at the first yield
.
Next is a decorator called "decorate":
The purpose of this is to avoid having to write f = decorate(g)
in the body of your code, and instead be able to decorate the function g
right where it's declared.
I wrote this meta-decorator to fix a tricky bug with the next one: "bounce".
It's a way of writing tail-recursive functions (with "yield" instead of "return"), and using some coroutine trickery to convert them into iterative steps: i.e. evaluating the functions to arbitrary depth without blowing the stack. I think this is super neat, and it'll be useful for if I've written too much OCaml recently and can't think of any ways to solve problems except with recursion.
As an example, here's a function which blows the stack:
And here's the equivalent function with my bounce
decorator applied:
Clean enough, right? I'm happy with how it turned out, and it's easy enough to include the decorate
and bounce
implementations either in the file itself, or imported from -- let's be honest -- utils.py
.