Как пони (ОРМ) делает свои трюки?

пони ОРМ делает хороший трюк преобразования выражения генератора в 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: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-запрос в три этапа:

  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

на 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() функция работает.

на