programing

포니(ORM)는 어떻게 속임수를 쓸까요?

stoneblock 2023. 7. 23. 13:56

포니(ORM)는 어떻게 속임수를 쓸까요?

포니 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은 훌륭한 자기 성찰과 메타프로그래밍이 내장되어 있다는 것을 알지만, 이 라이브러리는 어떻게 전처리 없이 생성기 표현을 번역할 수 있습니까?그것은 마법처럼 보입니다.

[업데이트]

블렌더 작성:

여기 당신이 찾고 있는 파일이 있습니다.일부 검사 마법사를 사용하여 제너레이터를 재구성하는 것 같습니다.파이썬의 구문을 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

은 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은 생성기와 람다만 분해하면 되기 때문에, 생성기에 대한 명령 흐름이 비교적 간단하기 때문에, 이것은 그렇게 복잡하지 않습니다 - 단지 중첩된 루프의 묶음일 뿐입니다.

현재 Pony ORM은 다음 두 가지를 제외한 전체 제너레이터 명령 집합을 다룹니다.

  1. 인라인(식인 경우):a if b else c
  2. 복합 비교:a < b < c

만약 포니가 그런 표현을 만난다면 그것은 그것을 상승시킵니다.NotImplementedError예외.그러나 이 경우에도 생성자 식을 문자열로 전달하여 작동할 수 있습니다.생성기를 문자열로 전달할 때 포니는 디컴파일러 모듈을 사용하지 않습니다.대신 표준 파이썬을 사용하여 AST를 얻습니다.compiler.parse기능.

이것이 당신의 질문에 대답하기를 바랍니다.

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