L'autore di Pony ORM è qui.
Pony traduce generatore di Python in query SQL in tre fasi:
- decompilazione del generatore di bytecode e ricostruzione generatore AST (albero di sintassi astratta)
- Traduzione di Python AST in "SQL astratto" - universale rappresentazione della lista a base di una query SQL
- Conversione rappresentazione astratta SQL in particolare dialetto SQL banca dati-dipendente
La parte più complessa è il secondo passaggio, in cui Pony deve comprendere il "significato" delle espressioni Python. Sembra che tu sia più interessato al primo passaggio, , quindi lascia che ti spieghi come funziona la decompilazione.
Consideriamo questa query:
>>> from pony.orm.examples.estore import *
>>> select(c for c in Customer if c.country == 'USA').show()
Quale sarà tradotto nelle seguenti SQL:
SELECT "c"."id", "c"."email", "c"."password", "c"."name", "c"."country", "c"."address"
FROM "Customer" "c"
WHERE "c"."country" = 'USA'
E sotto è il risultato di questa query che verrà stampata:
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
La funzione select()
accetta un generatore python come argomento, e t gallina analizza il suo bytecode. Siamo in grado di ottenere le istruzioni bytecode di questo generatore utilizzando standard di Python dis
modulo:
>>> 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 ha la funzione decompile()
entro modulo pony.orm.decompiling
che può ripristinare un AST dal bytecode:
>>> from pony.orm.decompiling import decompile
>>> ast, external_names = decompile(gen)
Qui, possiamo vedere la rappresentazione testuale dei nodi AST:
>>> ast
GenExpr(GenExprInner(Name('c'), [GenExprFor(AssName('c', 'OP_ASSIGN'), Name('.0'),
[GenExprIf(Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))]))])]))
Vediamo ora come funziona la funzione decompile()
.
La funzione decompile()
crea un oggetto Decompiler
che implementa il pattern Visitor. L'istanza del decompilatore riceve istruzioni bytecode una per una. Per ogni istruzione l'oggetto decompilatore chiama il proprio metodo. Il nome di questo metodo è uguale al nome dell'istruzione bytecode corrente.
Quando Python calcola un'espressione, utilizza lo stack, che memorizza un risultato intermedio di calcolo . Anche l'oggetto decompilatore ha il proprio stack, ma questo stack non memorizza il risultato del calcolo dell'espressione, ma il nodo AST per l'espressione.
Quando metodo decompilatore per la successiva istruzione bytecode viene chiamato, ci vuole nodi AST dalla pila, combina le in un nuovo nodo AST, e quindi pone questo nodo in cima alla pila.
Ad esempio, vediamo come viene calcolata la sottoespressione c.country == 'USA'
. Il frammento corrispondente bytecode è:
9 LOAD_FAST 1 (c)
12 LOAD_ATTR 0 (country)
15 LOAD_CONST 0 ('USA')
18 COMPARE_OP 2 (==)
Quindi, l'oggetto decompilatore esegue le seguenti operazioni:
- chiamate
decompiler.LOAD_FAST('c')
. Questo metodo mette il nodo Name('c')
nella parte superiore della pila del decompilatore.
- chiamate
decompiler.LOAD_ATTR('country')
. Questo metodo prende il nodo Name('c')
dallo stack, crea il nodo Geattr(Name('c'), 'country')
e lo mette in cima allo stack.
- Chiamate
decompiler.LOAD_CONST('USA')
. Questo metodo mette il nodo Const('USA')
in cima allo stack.
- Chiamate
decompiler.COMPARE_OP('==')
. Questo metodo prende due nodi (Getattr e Const) dallo stack, e quindi inserisce Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))])
in cima allo stack.
Dopo che tutte le istruzioni bytecode vengono elaborati, la pila decompiler contiene un singolo nodo AST che corrisponde a tutta generatore di espressione.
Da Pony ORM deve decompilare generatori e lambda solo, questo non è quel complesso, perché il flusso di istruzioni per un generatore è relativamente semplice - è solo un gruppo di cicli nidificati.
Attualmente Pony ORM copre l'intero istruzioni del generatore set, tranne due cose:
- Inline se le espressioni:
a if b else c
- confronti composti:
a < b < c
Se Pony incontra tale espressione solleva la NotImplementedError
eccezione. Ma anche nel caso puoi farlo funzionare passando l'espressione del generatore come una stringa. Quando si passa un generatore come stringa, Pony non usa il modulo decompilatore. Invece ottiene AST usando la funzione standard Python compiler.parse
.
Spero che questo risponda alla tua domanda.
Presumibilmente l'oggetto 'p' è un oggetto di un tipo implementato da Pony che esamina a quali metodi/proprietà si sta accedendo su di esso (ad esempio,' name', 'startswith') e li converte in SQL. – BrenBarn
[Qui] (https://github.com/ponyorm/pony/blob/orm/pony/orm/decompiling.py#L52) è il file che stai cercando. Sembra ricostruire il generatore usando un po 'di magia introspettiva. Non sono sicuro che supporti il 100% della sintassi di Python, ma è piuttosto interessante. – Blender
@Blender: ho visto questo tipo di trucco in LISP: tirare questa acrobazia in Python è semplicemente un male! –