¿Cómo hace Pony (ORM) sus trucos?

Pony ORM hace el buen truco de convertir una expresión de generador a SQL. Ejemplo:

>>> 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]] >>> 

Sé que Python tiene una introspección y metaprogtwigción maravillosas integradas, pero, ¿cómo esta biblioteca puede traducir la expresión del generador sin preprocesamiento? Parece magia.

[actualizar]

Blender escribió:

Aquí está el archivo que estás buscando. Parece reconstruir el generador usando alguna magia de introspección. No estoy seguro de si es compatible con el 100% de la syntax de Python, pero esto es bastante bueno. – Licuadora

    Pensaba que estaban explorando alguna característica del protocolo de expresión del generador, pero mirando este archivo y viendo el módulo ast involucrado … No, no están inspeccionando la fuente del progtwig sobre la marcha, ¿verdad? Alucinante …

    @BrenBarn: si bash llamar al generador fuera de la llamada a la función de select , el resultado es:

     >>> x = (p for p in Person if p.age > 20) >>> x.next() Traceback (most recent call last): File "", line 1, in  File "", line 1, in  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 >>> 

    Parece que están haciendo más encantamientos arcanos como inspeccionar la llamada a la función de select y procesar el árbol de gramática de syntax abstracta de Python sobre la marcha.

    Todavía me gustaría ver a alguien explicándolo, la fuente está muy por encima de mi nivel de magia.

    Related of "¿Cómo hace Pony (ORM) sus trucos?"

    El autor de Pony ORM está aquí.

    Pony convierte el generador de Python en una consulta SQL en tres pasos:

    1. Descomstackción del código de bytes del generador y la reconstrucción del generador AST (árbol de syntax abstracta)
    2. Traducción de Python AST a “SQL abstracto”: representación universal basada en listas de una consulta SQL
    3. Convertir la representación abstracta de SQL en un dialecto de SQL específico dependiente de la base de datos

    La parte más compleja es el segundo paso, donde Pony debe entender el “significado” de las expresiones de Python. Parece que estás más interesado en el primer paso, así que déjame explicarte cómo funciona la descomstackción.

    Consideremos esta consulta:

     >>> from pony.orm.examples.estore import * >>> select(c for c in Customer if c.country == 'USA').show() 

    La cual será traducida al siguiente SQL:

     SELECT "c"."id", "c"."email", "c"."password", "c"."name", "c"."country", "c"."address" FROM "Customer" "c" WHERE "c"."country" = 'USA' 

    Y a continuación se muestra el resultado de esta consulta que se imprimirá:

     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 

    La función select() acepta un generador de python como argumento, y luego analiza su código de bytes. Podemos obtener instrucciones de bytecode de este generador usando el módulo estándar de 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 

    Pony ORM tiene la función decompile() dentro del módulo pony.orm.decompiling que puede restaurar un AST desde el bytecode:

     >>> from pony.orm.decompiling import decompile >>> ast, external_names = decompile(gen) 

    Aquí, podemos ver la representación textual de los nodos AST:

     >>> ast GenExpr(GenExprInner(Name('c'), [GenExprFor(AssName('c', 'OP_ASSIGN'), Name('.0'), [GenExprIf(Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))]))])])) 

    Veamos ahora cómo funciona la función decompile() .

    La función decompile() crea un objeto Decompiler , que implementa el patrón de visitante. La instancia del decomstackdor recibe instrucciones de bytecode una por una. Para cada instrucción, el objeto decomstackdor llama a su propio método. El nombre de este método es igual al nombre de la instrucción de código de byte actual.

    Cuando Python calcula una expresión, utiliza la stack, que almacena un resultado intermedio de cálculo. El objeto del descomstackdor también tiene su propia stack, pero esta stack no almacena el resultado del cálculo de la expresión, sino el nodo AST para la expresión.

    Cuando se llama al método de descomstackdor para la siguiente instrucción de bytecode, toma los nodos AST de la stack, los combina en un nuevo nodo AST y luego coloca este nodo en la parte superior de la stack.

    Por ejemplo, veamos cómo se calcula la subexpresión c.country == 'USA' . El fragmento de código de bytes correspondiente es:

      9 LOAD_FAST 1 (c) 12 LOAD_ATTR 0 (country) 15 LOAD_CONST 0 ('USA') 18 COMPARE_OP 2 (==) 

    Entonces, el objeto decomstackdor hace lo siguiente:

    1. Llama a decompiler.LOAD_FAST('c') . Este método coloca el nodo Name('c') en la parte superior de la stack del descomstackdor.
    2. Llama a decompiler.LOAD_ATTR('country') . Este método toma el nodo Name('c') de la stack, crea el Geattr(Name('c'), 'country') y lo coloca en la parte superior de la stack.
    3. Llama a decompiler.LOAD_CONST('USA') . Este método coloca el nodo Const('USA') en la parte superior de la stack.
    4. Llama a decompiler.COMPARE_OP('==') . Este método toma dos nodos (Getattr y Const) de la stack, y luego pone Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))]) en la parte superior de la stack.

    Una vez que se procesan todas las instrucciones de bytecode, la stack del descomstackdor contiene un solo nodo AST que corresponde a la expresión del generador completo.

    Debido a que Pony ORM necesita descomstackr los generadores y las lambdas, esto no es tan complejo, porque el flujo de instrucciones para un generador es relativamente sencillo: es solo un grupo de bucles nesteds.

    Actualmente Pony ORM cubre todo el conjunto de instrucciones del generador, excepto dos cosas:

    1. Inline si expresiones: a if b else c
    2. Comparaciones compuestas: a < b < c

    Si Pony encuentra dicha expresión, genera la excepción NotImplementedError . Pero incluso en este caso puede hacer que funcione pasando la expresión del generador como una cadena. Cuando se pasa un generador como una cadena, Pony no usa el módulo de descomstackdor. En su lugar, obtiene el AST utilizando la función compiler.parse Python estándar.

    Espero que esto responda a su pregunta.