Pony ORM fait le bon truc de convertir une expression de générateur en SQL. Exemple:
>>> 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]]
>>>
Je sais que Python a une introspection et une métaprogrammation merveilleuses intégrées, mais comment cette bibliothèque est-elle capable de traduire l'expression du générateur sans prétraitement? Cela ressemble à de la magie.
[mettre à jour]
Blender a écrit:
Voici le fichier que vous recherchez. Il semble reconstruire le générateur en utilisant une certaine magie d'introspection. Je ne sais pas s'il prend en charge 100% de la syntaxe de Python, mais c'est plutôt cool. - Mixeur
Je pensais qu'ils exploraient une fonctionnalité du protocole d'expression du générateur, mais en regardant ce fichier et en voyant le ast
module impliqué ... Non, ils n'inspectent pas la source du programme à la volée, n'est-ce pas? Époustouflant ...
@BrenBarn: Si j'essaye d'appeler le générateur en dehors de l' select
appel de fonction, le résultat est:
>>> 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
>>>
On dirait qu'ils font des incantations plus obscures comme l'inspection de l' select
appel de fonction et le traitement de l'arbre de grammaire de la syntaxe abstraite Python à la volée.
J'aimerais toujours voir quelqu'un l'expliquer, la source est bien au-delà de mon niveau de sorcellerie.
p
objet est un objet d'un type mis en œuvre par Pony qui se penche sur les méthodes / propriétés sont accessibles à ce sujet (par exemplename
,startswith
) et les convertit en SQL.Réponses:
L'auteur de Pony ORM est ici.
Pony traduit le générateur Python en requête SQL en trois étapes:
La partie la plus complexe est la deuxième étape, où Pony doit comprendre la «signification» des expressions Python. Il semble que vous soyez le plus intéressé par la première étape, alors laissez-moi vous expliquer comment fonctionne la décompilation.
Considérons cette requête:
Ce qui sera traduit dans le SQL suivant:
Et ci-dessous est le résultat de cette requête qui sera imprimée:
La
select()
fonction accepte un générateur python comme argument, puis analyse son bytecode. Nous pouvons obtenir les instructions bytecode de ce générateur en utilisant ledis
module python standard :Pony ORM a la fonction
decompile()
dans le modulepony.orm.decompiling
qui peut restaurer un AST à partir du bytecode:Ici, nous pouvons voir la représentation textuelle des nœuds AST:
Voyons maintenant comment
decompile()
fonctionne la fonction.La
decompile()
fonction crée unDecompiler
objet qui implémente le modèle Visiteur. L'instance du décompilateur reçoit les instructions de bytecode une par une. Pour chaque instruction, l'objet décompilateur appelle sa propre méthode. Le nom de cette méthode est égal au nom de l'instruction de bytecode actuelle.Lorsque Python calcule une expression, il utilise stack, qui stocke un résultat intermédiaire de calcul. L'objet décompilateur a également sa propre pile, mais cette pile ne stocke pas le résultat du calcul de l'expression, mais le nœud AST pour l'expression.
Lorsque la méthode de décompilation pour l'instruction de bytecode suivante est appelée, elle prend les nœuds AST de la pile, les combine dans un nouveau nœud AST, puis place ce nœud au sommet de la pile.
Par exemple, voyons comment la sous
c.country == 'USA'
- expression est calculée. Le fragment de bytecode correspondant est:Ainsi, l'objet décompilateur effectue les opérations suivantes:
decompiler.LOAD_FAST('c')
. Cette méthode place leName('c')
nœud au sommet de la pile du décompilateur.decompiler.LOAD_ATTR('country')
. Cette méthode prend leName('c')
nœud de la pile, crée leGeattr(Name('c'), 'country')
nœud et le place au sommet de la pile.decompiler.LOAD_CONST('USA')
. Cette méthode place leConst('USA')
nœud au-dessus de la pile.decompiler.COMPARE_OP('==')
. Cette méthode prend deux nœuds (Getattr et Const) de la pile, puis les placeCompare(Getattr(Name('c'), 'country'), [('==', Const('USA'))])
en haut de la pile.Une fois toutes les instructions de bytecode traitées, la pile de décompilateur contient un seul nœud AST qui correspond à l'expression entière du générateur.
Puisque Pony ORM doit décompiler uniquement les générateurs et les lambdas, ce n'est pas si complexe, car le flux d'instructions pour un générateur est relativement simple - il ne s'agit que d'un tas de boucles imbriquées.
Actuellement, Pony ORM couvre l'ensemble des instructions du générateur, à l'exception de deux choses:
a if b else c
a < b < c
Si Pony rencontre une telle expression, il lève l'
NotImplementedError
exception. Mais même dans ce cas, vous pouvez le faire fonctionner en passant l'expression du générateur sous forme de chaîne. Lorsque vous passez un générateur sous forme de chaîne, Pony n'utilise pas le module décompilateur. Au lieu de cela, il obtient l'AST en utilisant lacompiler.parse
fonction Python standard .J'espère que ça répond à ta question.
la source