Pony ORM faz o belo truque de converter uma expressão geradora em SQL. Exemplo:
>>> 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]]
>>>
Eu sei que Python tem introspecção e metaprogramação maravilhosas embutidas, mas como esta biblioteca é capaz de traduzir a expressão geradora sem pré-processamento? Parece mágica.
[atualizar]
O Blender escreveu:
Aqui está o arquivo que você procura. Parece reconstruir o gerador usando alguma magia de introspecção. Não tenho certeza se ele suporta 100% da sintaxe do Python, mas isso é muito legal. - Liquidificador
Eu estava pensando que eles estavam explorando algum recurso do protocolo de expressão do gerador, mas olhando este arquivo e vendo o ast
módulo envolvido ... Não, eles não estão inspecionando o código-fonte do programa em tempo real, estão? Surpreendente...
@BrenBarn: Se eu tentar chamar o gerador fora do select
chamada de função, o resultado é:
>>> 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 eles estão fazendo encantamentos mais misteriosos, como inspecionar o select
chamada de função e processar a árvore gramatical de sintaxe abstrata do Python em tempo real.
Eu ainda gostaria de ver alguém explicando isso, a fonte está muito além do meu nível de magia.
p
objeto é um objeto de um tipo implementado por Pony que olha para o que métodos / propriedades estão sendo acessados nele (por exemplo,name
,startswith
) e os converte em SQL.Respostas:
O autor Pony ORM está aqui.
O Pony traduz o gerador Python em consulta SQL em três etapas:
A parte mais complexa é a segunda etapa, onde Pony deve entender o "significado" das expressões Python. Parece que você está mais interessado na primeira etapa, então deixe-me explicar como funciona a descompilação.
Vamos considerar esta consulta:
Que será traduzido no seguinte SQL:
E a seguir está o resultado dessa consulta que será impressa:
A
select()
função aceita um gerador Python como argumento e analisa seu bytecode. Podemos obter instruções de bytecode deste gerador usando odis
módulo Python padrão :Pony ORM tem a função
decompile()
dentro do módulopony.orm.decompiling
que pode restaurar um AST do bytecode:Aqui, podemos ver a representação textual dos nós AST:
Vamos agora ver como o
decompile()
função funciona.A
decompile()
função cria umDecompiler
objeto, que implementa o padrão Visitor. A instância do decompiler obtém instruções de bytecode uma a uma. Para cada instrução, o objeto descompilador chama seu próprio método. O nome deste método é igual ao nome da instrução bytecode atual.Quando o Python calcula uma expressão, ele usa pilha, que armazena um resultado intermediário do cálculo. O objeto descompilador também tem sua própria pilha, mas essa pilha não armazena o resultado do cálculo da expressão, mas o nó AST da expressão.
Quando o método descompilador para a próxima instrução de bytecode é chamado, ele pega os nós AST da pilha, combina-os em um novo nó AST e então coloca esse nó no topo da pilha.
Por exemplo, vamos ver como a subexpressão
c.country == 'USA'
é calculada. O fragmento de bytecode correspondente é:Portanto, o objeto descompilador faz o seguinte:
decompiler.LOAD_FAST('c')
. Este método coloca oName('c')
nó no topo da pilha do descompilador.decompiler.LOAD_ATTR('country')
. Este método tira oName('c')
nó da pilha, cria oGeattr(Name('c'), 'country')
nó e o coloca no topo da pilha.decompiler.LOAD_CONST('USA')
. Este método coloca oConst('USA')
nó no topo da pilha.decompiler.COMPARE_OP('==')
. Este método pega dois nós (Getattr e Const) da pilha e, em seguida, colocaCompare(Getattr(Name('c'), 'country'), [('==', Const('USA'))])
no topo da pilha.Depois que todas as instruções de bytecode são processadas, a pilha do descompilador contém um único nó AST que corresponde a toda a expressão do gerador.
Como o Pony ORM precisa descompilar apenas geradores e lambdas, isso não é tão complexo, porque o fluxo de instruções para um gerador é relativamente simples - é apenas um monte de loops aninhados.
Atualmente Pony ORM cobre todo o conjunto de instruções do gerador, exceto duas coisas:
a if b else c
a < b < c
Se Pony encontrar tal expressão, ele gerará a
NotImplementedError
exceção. Mas, mesmo neste caso, você pode fazer funcionar passando a expressão do gerador como uma string. Quando você passa um gerador como string, o Pony não usa o módulo descompilador. Em vez disso, ele obtém o AST usando o Python padrãocompiler.parse
função .espero que isso responda sua pergunta.
fonte