Pony ORM hace el buen truco de convertir una expresión generadora en 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 maravillosa introspección y metaprogramación incorporada, pero ¿cómo esta biblioteca puede traducir la expresión del generador sin preprocesar? Parece magia.
[actualizar]
Blender escribió:
Aquí está el archivo que busca. Parece reconstruir el generador usando algo de magia de introspección. No estoy seguro de si es compatible con el 100% de la sintaxis de Python, pero esto es bastante bueno. - Licuadora
Estaba pensando que estaban explorando alguna característica del protocolo de expresión del generador, pero mirando este archivo y viendo el ast
módulo involucrado ... No, no están inspeccionando la fuente del programa sobre la marcha, ¿verdad? Alucinante ...
@BrenBarn: Si intento llamar al generador fuera de la select
llamada a la función, el resultado es:
>>> 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
>>>
Parece que están haciendo encantamientos más arcanos, como inspeccionar la select
llamada a la función y procesar el árbol gramatical de sintaxis abstracta de Python sobre la marcha.
Todavía me gustaría que alguien lo explicara, la fuente está mucho más allá de mi nivel de hechicería.
p
objeto es un objeto de un tipo implementado por Pony que mira qué métodos / propiedades se están accediendo en él (por ejemploname
,startswith
) y los convierte a SQL.Respuestas:
El autor de Pony ORM está aquí.
Pony traduce el generador de Python a una consulta SQL en tres pasos:
La parte más compleja es el segundo paso, donde Pony debe comprender 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 descompilación.
Consideremos esta consulta:
Que se traducirá al siguiente SQL:
Y a continuación se muestra el resultado de esta consulta que se imprimirá:
La
select()
función acepta un generador de Python como argumento y luego analiza su código de bytes. Podemos obtener instrucciones de código de bytes de este generador usando eldis
módulo estándar de Python :Pony ORM tiene la función
decompile()
dentro del módulopony.orm.decompiling
que puede restaurar un AST desde el código de bytes:Aquí, podemos ver la representación textual de los nodos AST:
Veamos ahora cómo
decompile()
funciona la función.La
decompile()
función crea unDecompiler
objeto, que implementa el patrón Visitor. La instancia del descompilador obtiene instrucciones de código de bytes una por una. Para cada instrucción, el objeto descompilador llama a su propio método. El nombre de este método es igual al nombre de la instrucción de código de bytes actual.Cuando Python calcula una expresión, usa pila, que almacena un resultado intermedio del cálculo. El objeto descompilador también tiene su propia pila, pero esta pila 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 descompilador para la siguiente instrucción de código de bytes, toma los nodos AST de la pila, los combina en un nuevo nodo AST y luego coloca este nodo en la parte superior de la pila.
Por ejemplo, veamos cómo
c.country == 'USA'
se calcula la subexpresión . El fragmento de código de bytes correspondiente es:Entonces, el objeto descompilador hace lo siguiente:
decompiler.LOAD_FAST('c')
. Este método coloca elName('c')
nodo en la parte superior de la pila del descompilador.decompiler.LOAD_ATTR('country')
. Este método toma elName('c')
nodo de la pila, crea elGeattr(Name('c'), 'country')
nodo y lo coloca en la parte superior de la pila.decompiler.LOAD_CONST('USA')
. Este método coloca elConst('USA')
nodo en la parte superior de la pila.decompiler.COMPARE_OP('==')
. Este método toma dos nodos (Getattr y Const) de la pila y luego los colocaCompare(Getattr(Name('c'), 'country'), [('==', Const('USA'))])
en la parte superior de la pila.Después de que se procesan todas las instrucciones de código de bytes, la pila del descompilador contiene un solo nodo AST que corresponde a toda la expresión del generador.
Dado que Pony ORM solo necesita descompilar generadores y lambdas, esto no es tan complejo, porque el flujo de instrucciones para un generador es relativamente sencillo: es solo un montón de bucles anidados.
Actualmente, Pony ORM cubre todo el conjunto de instrucciones del generador, excepto dos cosas:
a if b else c
a < b < c
Si Pony encuentra tal expresión, plantea la
NotImplementedError
excepción. Pero incluso en este caso, puede hacer que funcione pasando la expresión del generador como una cadena. Cuando pasas un generador como una cadena, Pony no usa el módulo descompilador. En su lugar, obtiene el AST utilizando lacompiler.parse
función estándar de Python .Espero que esto responda a su pregunta.
fuente