130 votes

Comment Poney (ORM) n'ses trucs?

Poney ORM fait le tour de nice de la conversion d'un générateur d'expression 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 est merveilleux d'introspection et de la métaprogrammation builtin, mais comment cette bibliothèque est en mesure de traduire le générateur d'expression sans prétraitement? Il ressemble à de la magie.

[mise à jour]

Blender a écrit:

Voici le fichier que vous êtes après. Il semble à reconstruire le générateur à l'aide de certains introspection de sorcellerie. Je ne suis pas sûr si elle prend en charge 100% de Python syntaxe, mais c'est assez sympa. – Blender

Je pensais qu'ils étaient en train d'étudier certaines fonctionnalité du générateur d'expression protocole, mais la recherche de ce fichier, et de voir l' ast module en cause... Non, ils ne sont pas inspecter le code source du programme à la volée, sont-ils? Hallucinant...

@BrenBarn: Si j'essaie d'appeler le générateur à l'extérieur de l' select appel de la 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
>>>

Semble qu'ils sont en train de faire plus obscures incantations comme l'inspection de l' select de l'appel de fonction et de traitement de Python syntaxe abstraite de la grammaire de l'arbre à la volée.

J'aimerais voir quelqu'un en l'expliquant, la source est au-delà de mes prouesses.

238voto

Alexander Kozlovsky Points 1220

Poney ORM auteur est ici.

Poney traduit Python générateur dans la requête SQL en trois étapes:

  1. La décompilation de générateur de bytecode et la reconstruction de générateur AST (arbre de syntaxe abstraite)
  2. Traduction de Python AST dans "résumé" SQL " - universal de liste à la représentation d'une requête SQL
  3. La conversion résumé SQL représentation spécifique base de données dépendant de dialecte SQL

La partie la plus complexe est la deuxième étape, où le Poney doit comprendre le "sens" d'expressions Python. Semble que vous êtes plus intéressé dans la première étape, alors laissez-moi vous expliquer comment la décompilation des œuvres.

Nous allons examiner cette requête:

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

Qui sera traduit dans le code SQL suivant:

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

Et ci-dessous est le résultat de cette requête qui sera imprimé:

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

L' select() fonction accepte un python générateur comme argument, et analyse ensuite son bytecode. Nous pouvons obtenir des instructions bytecode de ce générateur à l'aide de standards de python, dis module de:

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

Poney ORM a la fonction decompile() dans le module pony.orm.decompiling qui peut la restauration d'un AST à partir du bytecode:

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

Ici, on peut voir la représentation textuelle de l'AST nœuds:

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

Nous allons maintenant voir comment l' decompile() fonctionne.

L' decompile() fonction crée un Decompiler objet qui implémente le modèle Visiteur. Le decompiler instance bénéficie d'un bytecode instructions une par une. Pour chaque instruction de la decompiler objet d'appels de sa propre méthode. Le nom de cette méthode est égal au nom de l'actuel code binaire de l'instruction.

Quand Python calcule une expression, il utilise la pile, qui stocke un intermédiaire résultat de calcul. Le decompiler objet a également sa propre pile, mais cette pile stocke pas le résultat de l'expression de calcul, mais le nœud de l'AST pour l'expression.

Lorsque decompiler méthode pour la prochaine instruction bytecode est appelée, il prend les nœuds de l'AST à partir de la pile, les combine dans un nouveau nœud de l'AST, et met alors ce nœud sur le haut de la pile.

Par exemple, nous allons voir comment la sous-expression c.country == 'USA' est calculé. L' correspondant bytecode fragment est:

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

Ainsi, le decompiler objet est le suivant:

  1. Appels decompiler.LOAD_FAST('c'). Cette méthode met l' Name('c') noeud sur le haut de la decompiler pile.
  2. Appels decompiler.LOAD_ATTR('country'). Cette méthode prend l' Name('c') nœud de la pile, crée l' Geattr(Name('c'), 'country') nœud et le pose sur le haut de la pile.
  3. Appels decompiler.LOAD_CONST('USA'). Cette méthode met l' Const('USA') noeud sur le dessus de la pile.
  4. Appels decompiler.COMPARE_OP('=='). Cette méthode prend deux nœuds (Getattr et Const) à partir de la pile, et puis met Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))]) sur le haut de la pile.

Après tout bytecode instructions sont traitées, le decompiler pile contient un seul nœud de l'AST qui correspond à l'ensemble du générateur d'expression.

Depuis Poney ORM besoins de décompiler des générateurs et les lambdas seulement, ce qui n'est pas complexe, parce que l'instruction de flux pour un générateur est relativement simple - c'est juste un tas de boucles imbriquées.

Actuellement Poney ORM couvre l'ensemble du générateur d'instructions à l'exception de deux choses:

  1. Inline si les expressions: a if b else c
  2. Composé de comparaisons: a < b < c

Si le Poney rencontre une telle expression, il soulève l' NotImplementedError d'exception. Mais, même dans ce cas, vous pouvez le faire fonctionner en passant par le générateur d'expression comme une chaîne de caractères. Lorsque vous passez un générateur comme une chaîne de Poney ne pas utiliser le decompiler module. Au lieu de cela il obtient l'AST en utilisant le standard de Python compiler.parse fonction.

Espérons que cela répond à votre question.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X