Ответ 1
Автор 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 |[email protected] |*** |John Smith |USA |address 1
2 |[email protected]|*** |Matthew Reed |USA |address 2
4 |[email protected]|*** |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
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
, который реализует шаблон посетителя.
Экземпляр декомпилятора получает команды bytecode один за другим.
Для каждой команды объект декомпилятора вызывает свой собственный метод.
Имя этого метода равно имени текущей инструкции байт-кода.
Когда Python вычисляет выражение, он использует стек, который хранит промежуточный результат расчета. Объект декомпилятора также имеет свой собственный стек, но этот стек хранит не результат вычисления выражения, но AST node для выражения.
Когда вызывается метод декомпилятора для следующей инструкции байт-кода, он принимает узлы AST из стека, объединяет их в новый AST node, а затем помещает этот node в верхнюю часть стека.
Например, посмотрим, как вычисляется подвыражение 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')
node в верхнюю часть стека декомпилятора. - Вызов
decompiler.LOAD_ATTR('country')
. Этот метод принимаетName('c')
node из стека, создаетGeattr(Name('c'), 'country')
node и помещает его в верхнюю часть стека. - Вызов
decompiler.LOAD_CONST('USA')
. Этот метод помещаетConst('USA')
node поверх стека. - Вызов
decompiler.COMPARE_OP('==')
. Этот метод принимает два узла (Getattr и Const) из стека, и затем помещаетCompare(Getattr(Name('c'), 'country'), [('==', Const('USA'))])
в верхней части стека.
После обработки инструкций байткода стек декомпилятора содержит один AST node, который соответствует всему выражению генератора.
Поскольку Pony ORM необходимо декомпилировать генераторы и только лямбда, это не так сложно, потому что поток команд для генератора относительно прост - это всего лишь куча вложенных циклов.
В настоящее время Pony ORM охватывает все команды генератора, за исключением двух вещей:
- Inline if выражения:
a if b else c
- Сравнительные сравнения:
a < b < c
Если Pony сталкивается с таким выражением, он вызывает исключение NotImplementedError
. Но даже в
в этом случае вы можете заставить его работать, передав выражение генератора в виде строки.
Когда вы передаете генератор в виде строки, Pony не использует модуль декомпилятора. Вместо
он получает AST, используя стандартную функцию Python compiler.parse
.
Надеюсь, это ответит на ваш вопрос.