Как пони (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 имеет замечательный встроенный самоанализ и метапрограммирование, но как эта библиотека может перевести выражение генератора без предварительной обработки? Это похоже на волшебство.
[обновление]
Blender писал:
вот файл что вы после. Кажется, чтобы реконструировать генератор, использующий некоторую интроспекцию волшебства. Я не уверен, что он поддерживает 100% синтаксиса Python, но это довольно круто. - Блендер
Я думал, что они изучают какую-то функцию из протокола выражения генератора, но смотрят этот файл и видят 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:Python27libsite-packagesponyormcore.py", line 1822, in next
% self.entity.__name__)
File "C:Python27libsite-packagesponyutils.py", line 92, in throw
raise exc
TypeError: Use select(...) function or Person.select(...) method for iteration
>>>
похоже, что они делают более тайные заклинания, такие как осмотр select
вызов функции и обработка дерева грамматики абстрактного синтаксиса Python на лету.
Я все еще хотел бы, чтобы кто-то объяснил это, источник находится далеко за пределами моего уровня волшебства.
1 ответ:
пони ОРМ автор здесь.
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
The
select()
функция принимает генератор Python в качестве аргумента, а затем анализирует байт-код. Мы можем получить байт-код инструкции этого генератора, используя стандартный 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
пони ОРМ имеет функцию
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()
функция работает.The