
The shortest record of asynchronous calls in tornado or patch bytecode in decorator
The complex asynchronous handler in tornado sometimes spreads to dozens of callback functions, which makes it difficult to perceive and modify code. Therefore, there is a tornado.gen module that allows you to write a handler as a generator. But a lot of yield gen.Task (...) doesn't look very good either. Therefore, in a fit of delirium, I wrote a decorator that simplifies writing:
As you already noticed, we replaced yield with << . Since python will not allow us to do this with standard tools, we need to modify the bytecode. For simple work with it, we will use the Byteplay module . Let's see the bytecode of two simple functions:
Therefore, we will make a simple patcher purely for this situation:
Now we have a bytecode almost identical to the bytecode of the gen function , apply it to shift and check the result:
The result is the same. The code for the general situation can be viewed on github . You can learn more about bytecode in the official documentation . In the meantime, we will return to tornado. Take the ready-made shortgen decorator . And write a simple handler:
The code has become a little better, but we still have to manually wrap the call in gen.Task , so we ’ll create another decorator to automate this process:
Now everything looks pretty decent, but how will it work with third-party libraries? But nothing, so now we need to patch them! No, we won’t patch the bytecode now, but we’ll just use the monkey patch. In order not to break the old code, we replace __getattribute__ for the necessary classes with:
Now if the patched object does not have an attribute, for example, find_e (the postfix _e was added so as not to break the old code), we will return the find attribute wrapped in the fasttgen decorator .
And now the code, for example, for asyncmongo, will look like this:
First, install the resulting module:
Now patch the classes we need:
Wrap our own asynchronous methods and functions in the decorator:
And use the handler:
A call can only set values for variables:
Complex decompressions are not supported:
Evilshortgen on github
Details about Byteplay bytecode
Before | After |
---|---|
|
|
How it works
As you already noticed, we replaced yield with << . Since python will not allow us to do this with standard tools, we need to modify the bytecode. For simple work with it, we will use the Byteplay module . Let's see the bytecode of two simple functions:
|
|
|
|
Therefore, we will make a simple patcher purely for this situation:
|
|
Now we have a bytecode almost identical to the bytecode of the gen function , apply it to shift and check the result:
|
|
The result is the same. The code for the general situation can be viewed on github . You can learn more about bytecode in the official documentation . In the meantime, we will return to tornado. Take the ready-made shortgen decorator . And write a simple handler:
def fetch(callback):
callback(1)
class Handler(BaseHandler):
@asynchronous
@gen.engine
@shortgen
def get(self):
result << gen.Task(fetch)
The code has become a little better, but we still have to manually wrap the call in gen.Task , so we ’ll create another decorator to automate this process:
def fastgen(fnc):
return partial(gen.Task, fnc)
@fastgen
def fetch(callback):
callback(1)
class Handler(BaseHandler):
@asynchronous
@gen.engine
@shortgen
def get(self):
result << fetch()
Now everything looks pretty decent, but how will it work with third-party libraries? But nothing, so now we need to patch them! No, we won’t patch the bytecode now, but we’ll just use the monkey patch. In order not to break the old code, we replace __getattribute__ for the necessary classes with:
def getattribute(self, name):
attr = None
if name.find('_e') == len(name) - 2:
attr = getattr(self, name[:-2])
if hasattr(attr, '__call__'):
return fastgen(attr)
else:
return super(self.__class__, self).__getattribute__(name)
Now if the patched object does not have an attribute, for example, find_e (the postfix _e was added so as not to break the old code), we will return the find attribute wrapped in the fasttgen decorator .
And now the code, for example, for asyncmongo, will look like this:
from asyncmongo.cursor import Cursor
Cursor.__getattribute__ = getattribute
class Handler(BaseHandler):
@asynchronous
@gen.engine
@shortgen
def get(self):
result, status << self.db.posts.find_e({'name': 'post'})
How to use it
First, install the resulting module:
pip install -e git+https://github.com/nvbn/evilshortgen.git#egg=evilshortgen
Now patch the classes we need:
from evilshortgen import shortpatch
shortpatch(Cls1, Cls2, Cls3)
Wrap our own asynchronous methods and functions in the decorator:
from evilshortgen import fastgen
@fastgen
def fetch(id, callback):
return callback(id)
And use the handler:
from evilshortgen import shortgen
class Handler(BaseHandler):
@asynchronous
@gen.engine
@shortgen
def get(self, id):
data << fetch(12)
num, user << Cls1.fetch()
Known Issues
A call can only set values for variables:
a << fetch() # работает
self.a << fetch() # не работает
Complex decompressions are not supported:
a, b << fetch() # работает
(a, b), c << fetch() # не работает
References
Evilshortgen on github
Details about Byteplay bytecode