私は記事
「トルネードでの非同期呼び出しの最も短い記録」またはデコレータのパッチバイトコードに非常に興味がありましたが、実用的な観点からではなく、実装の観点からです。
それでも、実行時のバイトコードの変更は非常に危険で信頼性の低い操作です。 確かに、代替のPythonインタープリターではサポートされていません。
この欠点を修正するために、これをはるかに意図しており、他の多くの言語で同様の目的に使用されています(LispまたはErlangで会ったばかりです)。 このメソッドは、抽象構文ツリー(AST)プログラムの修正版です。
手始めに-ASTとは? ASTは、コンパイル中のプログラムコードの中間表現であり、パーサーの出口で取得されます。
たとえば、このコード
def func(who): print "Hello, %s!" % who func()
次のASTに変換されます。
FunctionDef( name='func',
一見、何も明確ではありませんが、よく見ると、このツリーの要素の目的を推測できます。 AST(astモジュールの標準ライブラリで利用可能)を操作するための要素とツールの完全なドキュメントは
こちらです。
竜巻に戻りましょう。 元の記事と同じ表記法を使用してみましょう。
@shortgen
という名前のデコレータとバイナリシフト演算子
<<
。
元の記事と同じコード例を使用します。
準備する
トルネードをインストールする
mkdir tornado-shortgen cd tornado-shortgen/ virtualenv .env source .env/bin/activate pip install tornado
Tornadoを書きましょう-アプリケーション
import tornado.ioloop import tornado.web import tornado.gen import os class Handler(web.RequestHandler): @asynchronous @gen.engine @shortgen def get_short(self): (result, status) << self.db.posts.find_e({'name': 'post'}) @asynchronous @gen.engine def get(self): (result, status) = yield gen.Task(self.db.posts.find_e, {'name': 'post'}) application = tornado.web.Application([ (r"/", Handler), ]) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
shortgen_test.pyファイルに保存します
変換の実装
モジュールのASTを取得してみましょう。
$ python >>> import ast >>> print ast.dump(ast.parse(open("shortgen_test.py").read()))
書式化されていない長いテキストのフットクロスが表示されます。そこから、
get_short
関数と
get
関数の定義にのみ関心があります。
get_short
バイナリシフトおよびデコレータを使用したソース関数
FunctionDef( name='get_short', args=arguments(args=[Name(id='self', ctx=Param())], vararg=None, kwarg=None, defaults=[]), body=[ Expr(value=BinOp(
get
望ましい結果
FunctionDef( name='get', args=arguments(args=[Name(id='self', ctx=Param())], vararg=None, kwarg=None, defaults=[]), body=[ Assign(
怪しいように見えますが、なんと柔軟性があります! 実際、すべてがシンプルです。
違いを見てみましょう。
Expr
完全になくなったBinOp(left, op, right)
代わりにAssign(targets, value)
- 右オペランドの
ctx
値がLoad
からStore
変更されました - 呼び出し
self.db.posts.find_e(...)
gen.Task(self.db.posts.find_e, ...)
置き換えられました - 関数呼び出しの周りにyieldを追加
- デコレータがありません
@shortgen
したがって、最初から2番目を取得するには、
decorator_list
で@shortgen
decorator_list
を持つ関数を見つけます- このデコレータを削除
- 関数本体でバイナリシフト演算子
BinOp
を見つけます - 左右のオペランドを保存します。 左側で、
ctx
をStore
Load
に置き換え、右側のオペランドから関数名とその引数(positional、kw、および "star"-*、**)を抽出します。 - 関数の名前(
self.db.posts.find_e
)に最初の位置引数を追加します(つまり、この例では位置引数[self.db.posts.find_e, {'name': 'post'}]
を取得し、残りはすべて空です。 - 新しい
Call
作成しますが、これらの引数で既にgen.Task
関数を作成します - 収量で包む
Assign(targets, value)
を作成し、ターゲットとして以前に左のオペランドBinOp
を取り、値として-作成したばかりExpr
ソースツリーで、新しくExpr
Assign
置き換えます
複雑に聞こえますが、コードでは50行強かかりました。 不明な点がある場合は、そこを見てください。
これを実装する方法は? 何らかの種類のwhileループまたは再帰関数を使用して、額にソリューションを書き込むことができます。 ただし、Visitorパターンとその適応
ast.NodeTransformerを使用します
これは、
visit_FunctionDef
や
visit_Expr
などの
visit_[NodeType]
ようなメソッドを継承および作成できるクラス
visit_Expr
。 メソッドが返す値は、AST要素の新しい値になります。 そして、Visitor自体は単純に再帰的にツリーを走査し、対応する要素がツリー内で発生したときにメソッドを呼び出します。 これにより、コードをより便利に整理できます。
- 装飾された関数をキャッチするための
visit_FunctionDef
メソッドを作成します。 その中で、関数がデコレータでラップされていることを確認し、ラップされている場合はデコレータを削除してマークself.decorated
visit_Expression
メソッドを作成して、バイナリシフトをキャッチします。 その中で、 self.decorated
フラグがself.decorated
ていることと、 Expr
が正確にバイナリシフトであることを確認します。 残りの操作( Expr
からAssign
への変換)は手動で実行します。 幸いなことに、必要なデータはすべて近くにあります。
要点結果のASTは実行できます:
with open(filepath) as src: orig_ast = ast.parse(src.read()) new_ast = RewriteGenTask().visit(orig_ast) code = compile(new_ast, filename, 'exec') exec code
または.pyoファイルに保存します
stackoverflow.com/questions/8627835/generate-pyc-from-python-astgist.github.com/3849217#L172そして、インポートするか、
python my_module.pyo
呼び出します
おわりに
AST変換は、プログラムコードを変換するためのより信頼性の高い移植可能な方法です。 このような変換の記述は、バイトコードを変更するよりもはるかに簡単です。 このメソッドは、LispやErlangなど、多くの言語で広く使用されています。
2番目のプラス-パッチを適用する必要はありません。変換は、外部コードでも外部コードでも同じように機能します。
残りの長所と短所は
、元の記事に関する私の
解説で説明されてい
ます 。 繰り返しになりますが、主な欠点は、AST変換をその場で適用することが問題になることです。 コンパイル段階で.pycファイルに実装する必要があります。 (もちろん、そのようなハッキングを使用する場合は、これを適切に文書化する必要があります)。
この収量がいくつかの場所で書かれている小さなプロジェクトでは、そのような砂糖はあまり意味がありません。さらに、開発が複雑になります。 ファイルコンパイルの別の段階が表示されます。 しかし、大規模なトルネードプロジェクトでは、試すことができます。
参照資料
すべての要点コードASTドキュメントtornado.genのドキュメントASTから.pycファイルを生成するこれがすべて怖い松葉杖のように見える場合、xDがあります宿題