小马(ORM)如何操作?
小马ORM做转换生成器expression式到SQL的好方法。 例:
>>> 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]] >>>
我知道Python有很棒的内省和元编程内置,但是这个库如何能够在不进行预处理的情况下转换生成器expression式? 它看起来像魔术。
[更新]
Blender写道:
这是你之后的文件 。 似乎用一些内省魔法来重build发生器。 我不确定它是否支持Python的100%的语法,但这很酷。 – 搅拌机
我以为他们正在探索一些来自发生器expression式协议的特性,但看着这个文件,并看到了ast
模块…不,他们没有在飞行中检查程序源,是吗? 令人兴奋…
@BrenBarn:如果我试图在select
函数调用之外调用生成器,结果是:
>>> 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 >>>
看起来像他们正在做更多的神秘咒语,如检查select
函数调用和dynamic处理Python抽象语法文法树。
我仍然希望看到有人解释它,这个来源远远超出了我的魔法水平。
小马ORM作者在这里。
小马翻译Python生成器到SQL查询分三步:
- 反编译生成器字节码并重build生成器AST(抽象语法树)
- 将Python AST翻译成“抽象SQL” – 一个SQL查询的通用列表表示
- 将抽象SQL表示forms转换为特定的依赖于数据库的SQL方言
最复杂的部分是第二步,小马必须理解Pythonexpression式的“含义”。 似乎你最关心的第一步,所以让我解释一下反编译工作。
让我们考虑一下这个查询:
>>> 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
小马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()
函数创build一个实现Visitor模式的Decompiler
对象。 反编译器实例逐个获取字节码指令。 反编译器对象为每条指令调用自己的方法。 此方法的名称等于当前字节码指令的名称。
当Python计算一个expression式时,它使用堆栈,它存储了计算的中间结果。 反编译器对象也有自己的堆栈,但是这个堆栈不是存储expression式计算的结果,而是存储expression式的AST节点。
当调用下一个字节码指令的反编译器方法时,它从栈中取出AST节点,将它们组合成一个新的AST节点,然后把这个节点放在栈顶。
例如,让我们看看子expression式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')
节点,创buildGeattr(Name('c'), 'country')
节点并将它放在堆栈的顶部。 - 调用
decompiler.LOAD_CONST('USA')
。 此方法将Const('USA')
节点放在堆栈顶部。 - 调用
decompiler.COMPARE_OP('==')
。 此方法从堆栈中取两个节点(Getattr和Const),然后将Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))])
堆栈的顶部。
在所有的字节码指令被处理之后,反编译器堆栈包含一个对应于整个生成器expression式的单个AST节点。
由于Pony ORM只需要反编译生成器和lambdaexpression式,这并不复杂,因为生成器的指令stream是相对直接的 – 它只是一堆嵌套循环。
目前小马ORM涵盖了整个生成器指令集,除了两件事情:
- 内嵌expression式:
a if b else c
- 化合物比较:
a < b < c
如果Pony遇到这种expression式,会引发NotImplementedError
exception。 但即使在这种情况下,您可以通过将生成器expression式作为string传递来使其工作。 当您将string作为string传递时,Pony不使用反编译器模块。 相反,它使用标准的Python compiler.parse
函数来获取AST。
希望这回答你的问题。