Как пони (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 102

1 ответ:

пони ОРМ автор здесь.

Pony переводит генератор Python в SQL-запрос в три этапа:

  1. Декомпиляция байт-кода генератора и перестройка генератора AST (абстрактное синтаксическое дерево)
  2. перевод Python AST в "абстрактный SQL" -- универсальный представление SQL-запроса на основе списка
  3. преобразование абстрактного представления 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