小马(ORM)如何完成其​​花样?(How Pony (ORM) does its tricks?)

2019-08-31 14:46发布

小马ORM确实发电机式变换成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有精彩的自省和元编程内置的,但这个库是如何能够无需预处理翻译发电机体现在哪里? 它看起来像变魔术一样。

[更新]

搅拌机中写道:

下面是该文件 ,你是后。 看来使用一些魔法的反省重建发电机。 我不知道,如果它支持Python语法的100%,但是这是很酷。 - 搅拌机

我在想他们是从发电机表达协议探索了一些功能,但看这个文件,看见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函数调用和处理Python的抽象语法树语法上飞。

我还是想看到有人解释它的来源是远远超出了我的魔法水平。

Answer 1:

小马ORM笔者就在这里。

小马把Python发电机到SQL查询中的三个步骤:

  1. 生成的字节码进行反编译和重建产生AST(抽象语法树)
  2. Python的AST翻译成“抽象SQL” - 一个SQL查询的通用基于列表的表示
  3. 转换抽象SQL表示到特定数据库相关的SQL方言

最复杂的部分是第二步,在小马必须了解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这个生成的字节码指令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具有功能decompile()模块内pony.orm.decompiling其可以恢复从字节码的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()函数创建了一个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 (==)

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

  1. 调用decompiler.LOAD_FAST('c') 该方法提出的Name('c')节点上的反编译堆栈的顶部。
  2. 调用decompiler.LOAD_ATTR('country') 此方法采用的Name('c')节点从堆栈,造成Geattr(Name('c'), 'country')节点,并把它在堆栈的顶部。
  3. 调用decompiler.LOAD_CONST('USA') 该方法会将Const('USA')节点上的堆栈的顶部。
  4. 调用decompiler.COMPARE_OP('==') 该方法从堆栈有两个节点(GETATTR和const),并提出Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))])上在堆栈的顶部。

所有字节码指令被处理后,将反编译堆栈包含其对应于整个发电机表达单个AST节点。

由于小马ORM仅需要编译发电机和lambda表达式,这不是一个复杂的,因为发电机的指令流是相对简单 - 它只是一堆嵌套循环。

目前,小马ORM占地面积设置除了两件事情整个发电机说明:

  1. 内联如果表达式: a if b else c
  2. 化合物的比较: a < b < c

如果小马遇到这样表达它引发NotImplementedError例外。 但即使在这种情况下,你可以把它通过将发电机表达式作为字符串工作。 当你通过一台发电机作为字符串小马不使用反编译模块。 相反,它会使用标准的Python的AST compiler.parse功能。

希望这回答了你的问题。



文章来源: How Pony (ORM) does its tricks?