竜巻の非同期呼び出しまたはデコレータのパッチバイトコードの最短記録

竜巻の複雑な非同期ハンドラーは、数十個のコールバック関数に広がることがあり、コードの認識と変更が困難になります。 そのため、ハンドラーをジェネレーターとして作成できるtornado.genモジュールがあります。 しかし、多くのyield gen.Task(...)も見栄えがよくありません。 そのため、私はせん妄に合わせて、記述を簡単にするデコレータを作成しました。

@asynchronous @gen.engine def get(self): result, status = yield gen.Task( db.users.find_one, { '_id': ObjectId(user_id), }, )
      
      



 @asynchronous @gen.engine @shortgen def get(self): result, status << db.users.find_one_e({ '_id': ObjectId(user_id), }, )
      
      







仕組み



既にお気付きのとおり、 yield<<に置き換えました。 Pythonでは標準ツールでこれを行うことができないため、バイトコードを変更する必要があります。 単純な作業には、 Byteplayモジュールを使用します。 2つの単純な関数のバイトコードを見てみましょう。

 from byteplay import Code from pprint import pprint def gen(): a = yield 1 pprint(Code.from_code(gen.func_code).code)
      
      



 [(SetLineno, 5), #   5  (LOAD_CONST, 1), #   1 (YIELD_VALUE, None), # ""   (STORE_FAST, 'a'), #    a (LOAD_CONST, None), (RETURN_VALUE, None)]
      
      



 def shift(): a << 1 pprint(Code.from_code(shift.func_code).code)
      
      



 [(SetLineno, 10), (LOAD_GLOBAL, 'a'), # a    (LOAD_CONST, 1), #   1 (BINARY_LSHIFT, None), #     a (POP_TOP, None), #     (LOAD_CONST, None), (RETURN_VALUE, None)]
      
      





したがって、この状況のた​​めに単純なパッチャーを作成します。

 from byteplay import YIELD_VALUE, STORE_FAST code = Code.from_code(shift.func_code) code.code[3] = (YIELD_VALUE, None) code.code[4] = (STORE_FAST, 'a') code.code.pop(1) pprint(code.code)
      
      



 [(SetLineno, 10), (LOAD_CONST, 1), (YIELD_VALUE, None), (STORE_FAST, 'a'), (LOAD_CONST, None), (RETURN_VALUE, None)]
      
      





これで、 gen関数のバイトコードとほぼ同じバイトコードが得られ、それをシフトに適用して結果を確認します。

 shift.func_code = code.to_code() res_gen = gen().send(None) res_shift = shift().send(None) print res_gen print res_shift print res_gen == res_shift
      
      



 1 1 True
      
      





結果は同じです。 一般的な状況のコードはgithubで表示できます 。 バイトコードの詳細については、公式ドキュメントをご覧ください。 それまでの間、竜巻に戻ります。 既製のshortgenデコレータを取ります。 そして、簡単なハンドラーを書きます。



 def fetch(callback): callback(1) class Handler(BaseHandler): @asynchronous @gen.engine @shortgen def get(self): result << gen.Task(fetch)
      
      





コードは少し良くなりましたが、まだgen.Taskで呼び出しを手動でラップする必要があるため、このプロセスを自動化する別のデコレータを作成しましょう。



 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()
      
      





今ではすべてがかなりまともに見えますが、サードパーティのライブラリでどのように機能しますか? しかし、何もありませんので、パッチを適用する必要があります! いいえ、今はバイトコードにパッチを適用しませんが、サルパッチを使用します。 古いコードを壊さないために、必要なクラスの__getattribute__を次のように置き換えます。



 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)
      
      





パッチが適用されたオブジェクトに属性(たとえば、 find_e (古いコードを壊さないように接尾辞_eが追加された))がない場合、 fasttgenデコレータでラップされた検索属性を返します

そして今、たとえば、asyncmongoのコードは次のようになります。



 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'})
      
      





使い方



まず、結果のモジュールをインストールします。



 pip install -e git+https://github.com/nvbn/evilshortgen.git#egg=evilshortgen
      
      





次に、必要なクラスにパッチを適用します。



 from evilshortgen import shortpatch shortpatch(Cls1, Cls2, Cls3)
      
      





デコレータで独自の非同期メソッドと非同期関数をラップします。



 from evilshortgen import fastgen @fastgen def fetch(id, callback): return callback(id)
      
      





そして、ハンドラーを使用します。



 from evilshortgen import shortgen class Handler(BaseHandler): @asynchronous @gen.engine @shortgen def get(self, id): data << fetch(12) num, user << Cls1.fetch()
      
      





既知の問題



呼び出しでは、変数の値のみを設定できます。



 a << fetch() #  self.a << fetch() #  
      
      





複雑な解凍はサポートされていません。



 a, b << fetch() #  (a, b), c << fetch() #  
      
      





参照資料



GitHubのEvilshortgen

バイトコードの詳細

バイトプレイ



All Articles