Автор Pony ORM здесь.
Pony переводит генератор Python в SQL-запрос в три этапа:
- Декомпиляция байт-кода генератора и перестройка генератора AST (абстрактное синтаксическое дерево)
- Перевод Python AST в «абстрактный SQL» - универсальное представление SQL-запроса на основе списка.
- Преобразование абстрактного представления 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()
Функция принимает питона генератор в качестве аргумента, а затем анализирует его байт - код. Мы можем получить инструкции байт-кода этого генератора, используя стандартный dis
модуль python :
>>> 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 имеет функцию 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
объект, который реализует шаблон Visitor. Экземпляр декомпилятора получает инструкции байт-кода одну за другой. Для каждой инструкции объект декомпилятора вызывает свой собственный метод. Имя этого метода совпадает с именем текущей инструкции байт-кода.
Когда 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 (==)
Итак, объект декомпилятора делает следующее:
- Звонки
decompiler.LOAD_FAST('c')
. Этот метод помещает Name('c')
узел на вершину стека декомпилятора.
- Звонки
decompiler.LOAD_ATTR('country')
. Этот метод берет Name('c')
узел из стека, создает Geattr(Name('c'), 'country')
узел и помещает его на вершину стека.
- Звонки
decompiler.LOAD_CONST('USA')
. Этот метод помещает Const('USA')
узел в начало стека.
- Звонки
decompiler.COMPARE_OP('==')
. Этот метод берет два узла (Getattr и Const) из стека, а затем помещает их Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))])
на вершину стека.
После того, как все инструкции байт-кода обработаны, стек декомпилятора содержит единственный узел AST, который соответствует всему выражению генератора.
Поскольку Pony ORM требует декомпиляции только генераторов и лямбда-выражений, это не так уж сложно, потому что поток инструкций для генератора относительно прост - это просто набор вложенных циклов.
В настоящее время Pony ORM охватывает весь набор инструкций генератора, за исключением двух вещей:
- Встроенные выражения if:
a if b else c
- Составные сравнения:
a < b < c
Если Пони встречает такое выражение лица, это вызывает NotImplementedError
исключение. Но даже в этом случае вы можете заставить его работать, передав выражение генератора в виде строки. Когда вы передаете генератор как строку, Pony не использует модуль декомпилятора. Вместо этого он получает AST с помощью стандартной compiler.parse
функции Python .
Надеюсь, что это ответ на ваш вопрос.
p
объект является объектом типа реализуемого Пони , который смотрит на то , что методы / свойство в настоящее время доступны на нем (например,name
,startswith
) и преобразует их в SQL.