小马(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查询分三步:

  1. 反编译生成器字节码并重build生成器AST(抽象语法树)
  2. 将Python AST翻译成“抽象SQL” – 一个SQL查询的通用列表表示
  3. 将抽象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 (==) 

所以,反编译器对象执行以下操作:

  1. 调用decompiler.LOAD_FAST('c') 。 此方法将Name('c')节点放在反编译器堆栈的顶部。
  2. 调用decompiler.LOAD_ATTR('country') 。 这个方法从栈中取得Name('c')节点,创buildGeattr(Name('c'), 'country')节点并将它放在堆栈的顶部。
  3. 调用decompiler.LOAD_CONST('USA') 。 此方法将Const('USA')节点放在堆栈顶部。
  4. 调用decompiler.COMPARE_OP('==') 。 此方法从堆栈中取两个节点(Getattr和Const),然后将Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))])堆栈的顶部。

在所有的字节码指令被处理之后,反编译器堆栈包含一个对应于整个生成器expression式的单个AST节点。

由于Pony ORM只需要反编译生成器和lambdaexpression式,这并不复杂,因为生成器的指令stream是相对直接的 – 它只是一堆嵌套循环。

目前小马ORM涵盖了整个生成器指令集,除了两件事情:

  1. 内嵌expression式: a if b else c
  2. 化合物比较: a < b < c

如果Pony遇到这种expression式,会引发NotImplementedErrorexception。 但即使在这种情况下,您可以通过将生成器expression式作为string传递来使其工作。 当您将string作为string传递时,Pony不使用反编译器模块。 相反,它使用标准的Python compiler.parse函数来获取AST。

希望这回答你的问题。