Programming

포니 (ORM)는 어떻게 트릭을합니까?

procodes 2020. 8. 9. 10:41
반응형

포니 (ORM)는 어떻게 트릭을합니까?


Pony 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]]
>>>

파이썬에 훌륭한 인트로 스펙 션과 메타 프로그래밍이 내장되어 있다는 것을 알고 있지만,이 라이브러리가 어떻게 전처리없이 생성기 표현식을 번역 할 수 있습니까? 마법처럼 보입니다.

[최신 정보]

Blender는 다음과 같이 썼습니다.

다음은 당신이 찾는 파일 입니다. 내부 검사 마법을 사용하여 생성기를 재구성하는 것 같습니다. 파이썬 구문을 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 추상 구문 문법 트리를 즉석에서 처리하는 것과 같은 더 복잡한 주문을 수행하는 것처럼 보입니다 .

나는 아직도 누군가가 그것을 설명하는 것을보고 싶다. 근원은 나의 마법 수준을 훨씬 넘어선 것이다.


포니 ORM 작성자는 여기입니다.

Pony는 Python 생성기를 세 단계로 SQL 쿼리로 변환합니다.

  1. 생성기 바이트 코드 디 컴파일 및 생성기 AST 재 구축 (추상 구문 트리)
  2. Python AST를 "추상 SQL"로 번역-SQL 쿼리의 범용 목록 기반 표현
  3. 추상 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()함수는 파이썬 생성기를 인수로 받아들이고 바이트 코드를 분석합니다. 표준 파이썬 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은 바이트 코드에서 AST를 복원 할 수 decompile()있는 모듈 내 기능 pony.orm.decompiling가지고 있습니다.

>>> 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()함수가 어떻게 작동 하는지 살펴 보겠습니다 .

The decompile() function creates a Decompiler object, which implements the Visitor pattern. The decompiler instance gets bytecode instructions one-by-one. For each instruction the decompiler object calls its own method. The name of this method is equal to the name of current bytecode instruction.

When Python calculates an expression, it uses stack, which stores an intermediate result of calculation. The decompiler object also has its own stack, but this stack stores not the result of expression calculation, but AST node for the expression.

When decompiler method for the next bytecode instruction is called, it takes AST nodes from the stack, combines them into a new AST node, and then puts this node on the top of the stack.

For example, let's see how the subexpression c.country == 'USA' is calculated. The corresponding bytecode fragment is:

              9 LOAD_FAST                1 (c)
             12 LOAD_ATTR                0 (country)
             15 LOAD_CONST               0 ('USA')
             18 COMPARE_OP               2 (==)

So, the decompiler object does the following:

  1. Calls decompiler.LOAD_FAST('c'). This method puts the Name('c') node on the top of the decompiler stack.
  2. Calls decompiler.LOAD_ATTR('country'). This method takes the Name('c') node from the stack, creates the Geattr(Name('c'), 'country') node and puts it on the top of the stack.
  3. Calls decompiler.LOAD_CONST('USA'). This method puts the Const('USA') node on top of the stack.
  4. Calls decompiler.COMPARE_OP('=='). This method takes two nodes (Getattr and Const) from the stack, and then puts Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))]) on the top of the stack.

After all bytecode instructions are processed, the decompiler stack contains a single AST node which corresponds to the whole generator expression.

Since Pony ORM needs to decompile generators and lambdas only, this is not that complex, because the instruction flow for a generator is relatively straightforward - it is just a bunch of nested loops.

Currently Pony ORM covers the whole generator instructions set except two things:

  1. Inline if expressions: a if b else c
  2. Compound comparisons: a < b < c

If Pony encounters such expression it raises the NotImplementedError exception. But even in this case you can make it work by passing the generator expression as a string. When you pass a generator as a string Pony doesn't use the decompiler module. Instead it gets the AST using the standard Python compiler.parse function.

Hope this answers your question.

참고URL : https://stackoverflow.com/questions/16115713/how-pony-orm-does-its-tricks

반응형