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]]
>>>
Я знаю, что в Python есть замечательные встроенные функции самоанализа и метапрограммирования, но как эта библиотека может переводить выражение генератора без предварительной обработки? Похоже на магию.
[Обновить]
Блендер написал:
Вот файл, который вам нужен. Кажется, он реконструирует генератор, используя какое-то волшебство самоанализа. Я не уверен, поддерживает ли он 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:\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 на лету.
Я все еще хотел бы, чтобы кто-нибудь объяснил это, источник намного превышает мой уровень волшебства.
p
объект является объектом типа реализуемого Пони , который смотрит на то , что методы / свойство в настоящее время доступны на нем (например,name
,startswith
) и преобразует их в SQL.Ответы:
Автор Pony ORM здесь.
Pony переводит генератор Python в SQL-запрос в три этапа:
Самая сложная часть - это второй шаг, на котором Пони должен понять «значение» выражений Python. Кажется, вас больше всего интересует первый шаг, поэтому позвольте мне объяснить, как работает декомпиляция.
Рассмотрим этот запрос:
Что будет переведено в следующий SQL:
И ниже результат этого запроса, который будет распечатан:
select()
Функция принимает питона генератор в качестве аргумента, а затем анализирует его байт - код. Мы можем получить инструкции байт-кода этого генератора, используя стандартныйdis
модуль python :Pony ORM имеет функцию
decompile()
внутри модуля,pony.orm.decompiling
которая может восстанавливать AST из байт-кода:Здесь мы можем увидеть текстовое представление узлов AST:
Теперь посмотрим, как
decompile()
работает функция.decompile()
Функция создаетDecompiler
объект, который реализует шаблон Visitor. Экземпляр декомпилятора получает инструкции байт-кода одну за другой. Для каждой инструкции объект декомпилятора вызывает свой собственный метод. Имя этого метода совпадает с именем текущей инструкции байт-кода.Когда Python вычисляет выражение, он использует стек, в котором хранится промежуточный результат вычисления. Объект декомпилятора также имеет свой собственный стек, но в этом стеке хранится не результат вычисления выражения, а узел AST для выражения.
Когда вызывается метод декомпилятора для следующей инструкции байт-кода, он берет узлы AST из стека, объединяет их в новый узел AST, а затем помещает этот узел на вершину стека.
Например, давайте посмотрим, как
c.country == 'USA'
вычисляется подвыражение . Соответствующий фрагмент байт-кода:Итак, объект декомпилятора делает следующее:
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 охватывает весь набор инструкций генератора, за исключением двух вещей:
a if b else c
a < b < c
Если Пони встречает такое выражение лица, это вызывает
NotImplementedError
исключение. Но даже в этом случае вы можете заставить его работать, передав выражение генератора в виде строки. Когда вы передаете генератор как строку, Pony не использует модуль декомпилятора. Вместо этого он получает AST с помощью стандартнойcompiler.parse
функции Python .Надеюсь, что это ответ на ваш вопрос.
источник