Pony ORM Autor ist hier.
Pony übersetzt den Python-Generator in drei Schritten in eine SQL-Abfrage:
- Dekompilierung des Generator-Bytecodes und Neuerstellung des Generator-AST (abstrakter Syntaxbaum)
- Übersetzung von Python AST in "abstract SQL" - universelle listenbasierte Darstellung einer SQL-Abfrage
- Konvertieren der abstrakten SQL-Darstellung in einen bestimmten datenbankabhängigen SQL-Dialekt
Der komplexeste Teil ist der zweite Schritt, in dem Pony die "Bedeutung" von Python-Ausdrücken verstehen muss. Anscheinend interessiert Sie der erste Schritt am meisten. Lassen Sie mich erklären, wie das Dekompilieren funktioniert.
Betrachten wir diese Abfrage:
>>> from pony.orm.examples.estore import *
>>> select(c for c in Customer if c.country == 'USA').show()
Welches wird in die folgende SQL übersetzt:
SELECT "c"."id", "c"."email", "c"."password", "c"."name", "c"."country", "c"."address"
FROM "Customer" "c"
WHERE "c"."country" = 'USA'
Und unten ist das Ergebnis dieser Abfrage, die ausgedruckt wird:
id|email |password|name |country|address
--+-------------------+--------+--------------+-------+---------
1 |john@example.com |*** |John Smith |USA |address 1
2 |matthew@example.com|*** |Matthew Reed |USA |address 2
4 |rebecca@example.com|*** |Rebecca Lawson|USA |address 4
Die select()
Funktion akzeptiert einen Python-Generator als Argument und analysiert dann seinen Bytecode. Wir können Bytecode-Anweisungen dieses Generators mit dem Standard-Python- dis
Modul erhalten:
>>> 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
Pony ORM hat die Funktion decompile()
innerhalb des Moduls pony.orm.decompiling
, die einen AST aus dem Bytecode wiederherstellen kann:
>>> from pony.orm.decompiling import decompile
>>> ast, external_names = decompile(gen)
Hier sehen wir die Textdarstellung der AST-Knoten:
>>> ast
GenExpr(GenExprInner(Name('c'), [GenExprFor(AssName('c', 'OP_ASSIGN'), Name('.0'),
[GenExprIf(Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))]))])]))
Mal sehen, wie die decompile()
Funktion funktioniert.
Die decompile()
Funktion erstellt ein Decompiler
Objekt, das das Besuchermuster implementiert. Die Dekompilerinstanz erhält nacheinander Bytecode-Anweisungen. Für jede Anweisung ruft das Dekompilerobjekt eine eigene Methode auf. Der Name dieser Methode entspricht dem Namen der aktuellen Bytecode-Anweisung.
Wenn Python einen Ausdruck berechnet, wird ein Stapel verwendet, in dem ein Zwischenergebnis der Berechnung gespeichert wird. Das Dekompilerobjekt hat auch einen eigenen Stapel, aber dieser Stapel speichert nicht das Ergebnis der Ausdrucksberechnung, sondern den AST-Knoten für den Ausdruck.
Wenn die Dekompilierungsmethode für den nächsten Bytecode-Befehl aufgerufen wird, werden AST-Knoten vom Stapel genommen, zu einem neuen AST-Knoten kombiniert und dieser Knoten dann oben auf dem Stapel platziert.
Lassen Sie uns zum Beispiel sehen, wie der Unterausdruck c.country == 'USA'
berechnet wird. Das entsprechende Bytecode-Fragment lautet:
9 LOAD_FAST 1 (c)
12 LOAD_ATTR 0 (country)
15 LOAD_CONST 0 ('USA')
18 COMPARE_OP 2 (==)
Das Dekompilerobjekt führt also Folgendes aus:
- Anrufe
decompiler.LOAD_FAST('c')
. Diese Methode platziert den Name('c')
Knoten oben auf dem Dekompiler-Stapel.
- Anrufe
decompiler.LOAD_ATTR('country')
. Diese Methode nimmt den Name('c')
Knoten vom Stapel, erstellt den Geattr(Name('c'), 'country')
Knoten und legt ihn oben auf den Stapel.
- Anrufe
decompiler.LOAD_CONST('USA')
. Diese Methode legt den Const('USA')
Knoten oben auf den Stapel.
- Anrufe
decompiler.COMPARE_OP('==')
. Diese Methode nimmt zwei Knoten (Getattr und Const) aus dem Stapel und legt sie dann Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))])
oben auf den Stapel.
Nachdem alle Bytecode-Anweisungen verarbeitet wurden, enthält der Dekompiler-Stapel einen einzelnen AST-Knoten, der dem gesamten Generatorausdruck entspricht.
Da Pony ORM nur Generatoren und Lambdas dekompilieren muss, ist dies nicht so komplex, da der Befehlsfluss für einen Generator relativ einfach ist - es handelt sich nur um eine Reihe verschachtelter Schleifen.
Derzeit deckt Pony ORM den gesamten Generatorbefehlssatz ab, mit Ausnahme von zwei Dingen:
- Inline wenn Ausdrücke:
a if b else c
- Zusammengesetzte Vergleiche:
a < b < c
Wenn Pony auf einen solchen Ausdruck stößt, wird die NotImplementedError
Ausnahme ausgelöst . Aber auch in diesem Fall können Sie es zum Laufen bringen, indem Sie den Generatorausdruck als Zeichenfolge übergeben. Wenn Sie einen Generator als String übergeben, verwendet Pony das Dekompilierermodul nicht. Stattdessen wird der AST mit der Standard-Python- compiler.parse
Funktion abgerufen .
Hoffe das beantwortet deine Frage.
p
Objekt ein Objekt eines Typs von Pony implementiert , dass schaut, was Methoden / Eigenschaften auf sie zugegriffen wird ( zum Beispielname
,startswith
) und wandelt sie in SQL.