Pony ORM的作者在这里。
Pony将Python生成器翻译成SQL查询分为三个步骤:
- 反编译生成器字节码并重建生成器AST(抽象语法树)
- 将Python AST翻译成“抽象SQL”——一个SQL查询的通用基于列表的表示
- 将抽象SQL表示转换为特定于数据库的SQL方言
最复杂的部分是第二步,其中Pony必须理解Python表达式的“含义”。看起来您最感兴趣的是第一步,所以让我解释一下反编译的工作原理。
让我们考虑这个查询:
>>> from pony.orm.examples.estore import *
>>> select(c for c in Customer if c.country == 'USA').show()
将转换为以下SQL语句:
SELECT "c"."id", "c"."email", "c"."password", "c"."name", "c"."country", "c"."address"
FROM "Customer" "c"
WHERE "c"."country" = 'USA'
以下是将被打印出来的查询结果:
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
select()
函数接受一个Python生成器作为参数,然后分析其字节码。我们可以使用标准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在模块
pony.orm.decompiling
内提供了
decompile()
函数,可从字节码中恢复AST。
>>> from pony.orm.decompiling import decompile
>>> ast, external_names = decompile(gen)
在这里,我们可以看到AST节点的文本表示:
>>> ast
GenExpr(GenExprInner(Name('c'), [GenExprFor(AssName('c', 'OP_ASSIGN'), Name('.0'),
[GenExprIf(Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))]))])]))
现在我们来看看decompile()
函数的工作原理。
decompile()
函数创建了一个实现Visitor模式的Decompiler
对象。
反编译器实例逐个获取字节码指令。
对于每个指令,反编译器对象都会调用自己的方法。
这个方法的名称与当前字节码指令的名称相同。
当Python计算表达式时,它使用栈存储计算的中间结果。反编译器对象也有自己的栈,
但这个栈不是存储表达式计算结果,而是存储表达式的AST节点。
当下一个字节码指令的反编译器方法被调用时,
它从栈中取出AST节点,将它们组合成一个新的AST节点,然后将该节点放在栈顶。
例如,让我们看看子表达式c.country == 'USA'
如何计算。 相应的字节码片段为:
9 LOAD_FAST 1 (c)
12 LOAD_ATTR 0 (country)
15 LOAD_CONST 0 ('USA')
18 COMPARE_OP 2 (==)
所以,反编译器对象执行以下操作:
- 调用
decompiler.LOAD_FAST('c')
。
该方法将 Name('c')
节点置于反编译器堆栈的顶部。
- 调用
decompiler.LOAD_ATTR('country')
。
该方法从堆栈中取出 Name('c')
节点,
创建 Getattr(Name('c'), 'country')
节点并将其置于堆栈的顶部。
- 调用
decompiler.LOAD_CONST('USA')
。
该方法将 Const('USA')
节点置于堆栈的顶部。
- 调用
decompiler.COMPARE_OP('==')
。
该方法从堆栈中取出两个节点(Getattr 和 Const),
然后将 Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))])
置于堆栈的顶部。
在处理完所有字节码指令之后,反编译器堆栈包含一个与整个生成器表达式对应的 AST 节点。
由于 Pony ORM 只需要反编译生成器和 lambda 函数,因此这并不复杂,
因为生成器的指令流相对直接 - 它只是一堆嵌套的循环。
当前 Pony ORM 包含了除以下两个事项外的完整生成器指令集:
- 内联 if 表达式:
a if b else c
- 复合比较:
a < b < c
如果 Pony 遇到此类表达式,则会引发 NotImplementedError
异常。但即使在
这种情况下,您也可以通过将生成器表达式作为字符串传递来使其正常工作。
当您将生成器作为字符串传递时,Pony 不使用反编译器模块。相反,
它使用标准 Python 的 compiler.parse
函数获取 AST。
希望这回答了您的问题。
p
对象是由 Pony 实现的一种类型的对象,它查看正在访问它的方法/属性(例如name
、startswith
)并将它们转换为 SQL。 - BrenBarn