Pony ORM robi niezłą sztuczkę konwertowania wyrażenia generatora na SQL. Przykład:
>>> 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]]
>>>
Wiem, że Python ma wbudowane wspaniałe funkcje introspekcji i metaprogramowania, ale w jaki sposób ta biblioteka jest w stanie przetłumaczyć wyrażenie generatora bez wstępnego przetwarzania? Wygląda jak magia.
[aktualizacja]
Blender napisał:
Oto plik , którego szukasz. Wydaje się, że rekonstruuje generator przy użyciu pewnej magii introspekcji. Nie jestem pewien, czy obsługuje 100% składni Pythona, ale jest całkiem fajny. - Blender
Myślałem, że badają jakąś funkcję z protokołu wyrażeń generatora, ale patrząc na ten plik i widząc ast
zaangażowany moduł ... Nie, oni nie sprawdzają źródła programu w locie, prawda? Niesamowite ...
@BrenBarn: Jeśli spróbuję wywołać generator poza select
wywołaniem funkcji, wynik jest:
>>> 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
>>>
Wygląda na to, że wykonują bardziej tajemne zaklęcia, takie jak sprawdzanie select
wywołania funkcji i przetwarzanie drzewa gramatyki składni abstrakcyjnej w Pythonie w locie.
Nadal chciałbym zobaczyć, jak ktoś to wyjaśnia, źródło jest daleko poza moim poziomem magii.
p
obiekt jest obiektem typu zaimplementowanego przez Pony, który sprawdza, jakie metody / właściwości są na nim dostępne (np.name
,startswith
) I konwertuje je na SQL.Odpowiedzi:
Autor Pony ORM jest tutaj.
Pony tłumaczy generator Pythona na zapytanie SQL w trzech krokach:
Najbardziej złożoną częścią jest drugi krok, w którym Pony musi zrozumieć „znaczenie” wyrażeń Pythona. Wygląda na to, że najbardziej interesuje Cię pierwszy krok, więc pozwól mi wyjaśnić, jak działa dekompilacja.
Rozważmy to zapytanie:
Który zostanie przetłumaczony na następujący SQL:
A poniżej wynik tego zapytania, który zostanie wydrukowany:
select()
Funkcja przyjmuje generator pytona jako argumentu, i analizuje jego bajtowego. Możemy uzyskać instrukcje kodu bajtowego tego generatora za pomocą standardowegodis
modułu Pythona :Pony ORM ma funkcję
decompile()
w module,pony.orm.decompiling
która może przywrócić AST z kodu bajtowego:Tutaj możemy zobaczyć tekstową reprezentację węzłów AST:
Zobaczmy teraz, jak
decompile()
działa ta funkcja.decompile()
Funkcja tworzyDecompiler
obiekt, który implementuje odwiedzający. Instancja dekompilatora pobiera instrukcje kodu bajtowego jedna po drugiej. Dla każdej instrukcji obiekt dekompilatora wywołuje własną metodę. Nazwa tej metody jest taka sama, jak nazwa bieżącej instrukcji kodu bajtowego.Kiedy Python oblicza wyrażenie, używa stosu, który przechowuje pośredni wynik obliczeń. Obiekt dekompilatora również ma swój własny stos, ale ten stos przechowuje nie wynik obliczenia wyrażenia, ale węzeł AST dla wyrażenia.
Kiedy wywoływana jest metoda dekompilacji dla następnej instrukcji kodu bajtowego, pobiera węzły AST ze stosu, łączy je w nowy węzeł AST, a następnie umieszcza ten węzeł na szczycie stosu.
Na przykład zobaczmy, jak
c.country == 'USA'
obliczane jest podwyrażenie . Odpowiedni fragment kodu bajtowego to:Tak więc obiekt dekompilatora wykonuje następujące czynności:
decompiler.LOAD_FAST('c')
. Ta metoda umieszczaName('c')
węzeł na szczycie stosu dekompilatora.decompiler.LOAD_ATTR('country')
. Ta metoda pobieraName('c')
węzeł ze stosu, tworzyGeattr(Name('c'), 'country')
węzeł i umieszcza go na szczycie stosu.decompiler.LOAD_CONST('USA')
. Ta metoda umieszczaConst('USA')
węzeł na szczycie stosu.decompiler.COMPARE_OP('==')
. Ta metoda pobiera dwa węzły (Getattr i Const) ze stosu, a następnie umieszcza jeCompare(Getattr(Name('c'), 'country'), [('==', Const('USA'))])
na szczycie stosu.Po przetworzeniu wszystkich instrukcji kodu bajtowego stos dekompilatora zawiera pojedynczy węzeł AST, który odpowiada całemu wyrażeniu generatora.
Ponieważ Pony ORM musi dekompilować tylko generatory i lambdy, nie jest to takie skomplikowane, ponieważ przepływ instrukcji dla generatora jest stosunkowo prosty - jest to po prostu kilka zagnieżdżonych pętli.
Obecnie Pony ORM obejmuje cały zestaw instrukcji generatora z wyjątkiem dwóch rzeczy:
a if b else c
a < b < c
Jeśli Pony napotka takie wyrażenie, zgłosi
NotImplementedError
wyjątek. Ale nawet w tym przypadku możesz sprawić, by działało, przekazując wyrażenie generatora jako ciąg. Kiedy przekazujesz generator jako ciąg znaków, Pony nie używa modułu dekompilatora. Zamiast tego pobiera AST za pomocą standardowejcompiler.parse
funkcji Pythona .Mam nadzieję, że to odpowiada na twoje pytanie.
źródło