[解決済み] ポニー(ORM)はどのように機能するのか?
質問
ポニーORM はジェネレータ式をSQLに変換するという素晴らしいトリックを行います。例を挙げます。
>>> select(p for p in Person if p.name.startswith('Paul'))
.order_by(Person.name)[:2]
SELECT "p"."id", "p"."name", "p"."age"
FROM "Person" "p"
WHERE "p"."name" LIKE "Paul%"
ORDER BY "p"."name"
LIMIT 2
[Person[3], Person[1]]
>>>
Pythonには素晴らしいイントロスペクションやメタプログラミングが組み込まれていますが、このライブラリはどうやって前処理なしでジェネレータ式を翻訳しているのでしょうか?まるで魔法のようです。
[更新しました。]
Blenderが書きました。
以下はそのファイルです。 というファイルです。これは、いくつかのイントロスペクションのウィザードを使ってジェネレータを再構築しているようです。Pythonの構文を100%サポートしているかどうかは分かりませんが、これはかなりクールです。- ブレンダー
ジェネレータ式プロトコルから何か機能を探っているのかと思いきや、このファイルを見てみると
ast
モジュールが絡んでいる...。いや、まさかその場でプログラムソースを調べているわけではあるまいな。驚きだ...。
@BrenBarn です。の外でジェネレータを呼び出そうとすると
select
関数を呼び出すと、その結果は
>>> x = (p for p in Person if p.age > 20)
>>> x.next()
Traceback (most recent call last):
File "<interactive input>", line 1, in <module>
File "<interactive input>", line 1, in <genexpr>
File "C:\Python27\lib\site-packages\pony\orm\core.py", line 1822, in next
% self.entity.__name__)
File "C:\Python27\lib\site-packages\pony\utils.py", line 92, in throw
raise exc
TypeError: Use select(...) function or Person.select(...) method for iteration
>>>
を検査するような、より難解な呪文を唱えているように思えます。
select
関数呼び出しの検査や、Python の抽象構文文法ツリーをオンザフライで処理するような、より難解な呪文を唱えているようです。
私はまだ誰かがそれを説明するのを見たいと思います、ソースは私のウィザードリィレベルをはるかに超えています。
どのように解決するのですか?
ポニーORMの作者はこちらです。
Ponyは3つのステップでPythonのジェネレータをSQLクエリに変換します。
- ジェネレータのバイトコードをデコンパイルし、ジェネレータのASTを再構築する。 (抽象構文木)
- Python ASTから"abstract SQL"への変換 -- 汎用的な SQLクエリのリストベースの表現
- 抽象的なSQL表現を特定の データベース依存のSQL方言に変換
最も複雑な部分は2番目のステップで、PonyはPythonの式の"meaning"を理解しなければなりません。 Pythonの式の意味を理解する必要があります。あなたは最初のステップに最も興味があるようですね。 そこで、デコンパイルがどのように行われるかを説明します。
このクエリを考えてみましょう。
>>> from pony.orm.examples.estore import *
>>> select(c for c in Customer if c.country == 'USA').show()
これは以下のようなSQLに変換されます。
SELECT "c"."id", "c"."email", "c"."password", "c"."name", "c"."country", "c"."address"
FROM "Customer" "c"
WHERE "c"."country" = 'USA'
そして以下は、このクエリの結果がプリントアウトされたものです。
id|email |password|name |country|address
--+-------------------+--------+--------------+-------+---------
1 |[email protected] |*** |John Smith |USA |address 1
2 |[email protected]|*** |Matthew Reed |USA |address 2
4 |[email protected]|*** |Rebecca Lawson|USA |address 4
は
select()
関数は引数として python ジェネレータを受け取り、そのバイトコードを解析します。
このジェネレータのバイトコード命令は、標準的な python の
dis
モジュールを使って取得できます。
>>> gen = (c for c in Customer if c.country == 'USA')
>>> import dis
>>> dis.dis(gen.gi_frame.f_code)
1 0 LOAD_FAST 0 (.0)
>> 3 FOR_ITER 26 (to 32)
6 STORE_FAST 1 (c)
9 LOAD_FAST 1 (c)
12 LOAD_ATTR 0 (country)
15 LOAD_CONST 0 ('USA')
18 COMPARE_OP 2 (==)
21 POP_JUMP_IF_FALSE 3
24 LOAD_FAST 1 (c)
27 YIELD_VALUE
28 POP_TOP
29 JUMP_ABSOLUTE 3
>> 32 LOAD_CONST 1 (None)
35 RETURN_VALUE
ポニーORMは関数
decompile()
モジュール内で
pony.orm.decompiling
これは
バイトコードからASTを復元することができます。
>>> from pony.orm.decompiling import decompile
>>> ast, external_names = decompile(gen)
ここでは、ASTノードのテキスト表現を見ることができます。
>>> ast
GenExpr(GenExprInner(Name('c'), [GenExprFor(AssName('c', 'OP_ASSIGN'), Name('.0'),
[GenExprIf(Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))]))])]))
では、次に
decompile()
関数がどのように動作するかを見てみましょう。
は
decompile()
関数は
Decompiler
オブジェクトを生成し、Visitorパターンを実装します。
デコンパイラのインスタンスはバイトコード命令を一つずつ取得する。
各命令について、デコンパイラ・オブジェクトはそれ自身のメソッドを呼び出す。
このメソッドの名前は現在のバイトコード命令の名前と同じです。
Pythonが式を計算するとき、計算の中間結果を保存するスタック スタックには計算の中間結果が格納されます。デコンパイラオブジェクトもそれ自身のスタックを持っています。 しかし、このスタックには式の計算結果は格納されません。 式の計算結果ではなく、式のASTノードが格納される。
次のバイトコード命令に対するデコンパイラメソッドが呼ばれると スタックからASTノードを取り出して、それらを結合し を新しいASTノードに結合し、このノードをスタックの一番上に置く。
例えば、部分式
c.country == 'USA'
がどのように計算されるか見てみましょう。このとき
対応するバイトコードフラグメントは
9 LOAD_FAST 1 (c)
12 LOAD_ATTR 0 (country)
15 LOAD_CONST 0 ('USA')
18 COMPARE_OP 2 (==)
つまり、デコンパイラオブジェクトは次のようになります。
-
呼び出し
decompiler.LOAD_FAST('c')
. このメソッドはName('c')
ノードをデコンパイラのスタックの一番上に置きます。 -
呼び出し
decompiler.LOAD_ATTR('country')
. このメソッドはName('c')
ノードをスタックから取り出します。 を作成します。Geattr(Name('c'), 'country')
ノードを作成し、それをスタックの一番上に置く。 -
呼び出す
decompiler.LOAD_CONST('USA')
. このメソッドはConst('USA')
ノードをスタックの一番上に置きます。 -
呼び出し
decompiler.COMPARE_OP('==')
. このメソッドはスタックから2つのノード(GetattrとConst)を取ります。 そして、スタックからCompare(Getattr(Name('c'), 'country'), [('==', Const('USA'))])
をスタックの一番上に置きます。
すべてのバイトコード命令が処理された後、デコンパイラのスタックには ジェネレータ式全体に対応する単一のASTノードが含まれます。
ポニーORMはジェネレータとラムダのみをデコンパイルする必要があるので、これはそれほど複雑ではありません。 とラムダのみをデコンパイルする必要があるので、これはそれほど複雑ではありません。 ジェネレーターの命令フローは比較的単純で - ジェネレータの命令フローは比較的単純で、ネストされたループの束に過ぎないからです。
現在、Pony ORMは2つのことを除いて、ジェネレータの命令セット全体をカバーしています。
-
インラインの if 式。
a if b else c
-
複合比較。
a < b < c
もしポニーがこのような表現に遭遇した場合、ポニーは
NotImplementedError
例外を発生させます。しかし、この場合でも
この場合でも、ジェネレータ式を文字列として渡すことで動作させることができます。
ジェネレータを文字列として渡すと、Ponyはデコンパイラモジュールを使用しません。代わりに
を使用してASTを取得します。
compiler.parse
関数を用いてASTを取得します。
これがあなたの質問の答えになることを願っています。
関連
-
[解決済み] プログラムの実行やシステムコマンドの呼び出しはどのように行うのですか?
-
[解決済み] リストのリストからフラットなリストを作るには?
-
[解決済み] Pythonには文字列の'contains'サブストリングメソッドがありますか?
-
[解決済み] ORM(Object-Relational Mapping)における「N+1 selects問題」とは?
-
[解決済み】if __name__ == "__main__": は何をするのでしょうか?
-
[解決済み】ネストされたディレクトリを安全に作成するには?
-
[解決済み】Pythonに三項条件演算子はありますか?
-
[解決済み】2つの辞書を1つの式でマージする(辞書の和をとる)には?)
-
[解決済み] Spyderを仮想環境で動作させるには?
-
[解決済み] PyMongoで.sortを使用する
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] 文字列から先頭と末尾のスペースを削除するには?
-
[解決済み] ファブリック経由でデプロイユーザとしてvirtualenvを有効化する
-
[解決済み] PyMongoで.sortを使用する
-
[解決済み] Ctrl-CでPythonスクリプトを終了できない
-
[解決済み] 異なる順序で同じ要素を持つ2つのJSONオブジェクトを等しく比較するには?
-
[解決済み] subprocess.run()の出力を抑制またはキャプチャするには?
-
[解決済み] Flask でグローバル変数はスレッドセーフか?リクエスト間でデータを共有するには?
-
[解決済み] pycharmがタブをスペースに自動変換する
-
[解決済み] Python 言語を決定するには?
-
[解決済み] Alembicアップグレードスクリプトでインサートやアップデートを実行するにはどうすればよいですか?