Pony (ORM) hilelerini nasıl yapıyor?


111

Pony ORM , bir jeneratör ifadesini SQL'e dönüştürmenin güzel hilesini yapıyor. Misal:

>>> 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'un harika iç gözlem ve metaprogramlama yerleşiklerine sahip olduğunu biliyorum, ancak bu kitaplık jeneratör ifadesini ön işleme olmadan nasıl çevirebilir? Sihire benziyor.

[Güncelleme]

Blender şunu yazdı:

İşte peşinde olduğunuz dosya . Bazı iç gözlem sihirbazlığı kullanarak jeneratörü yeniden inşa ediyor gibi görünüyor. Python'un sözdiziminin% 100'ünü destekleyip desteklemediğinden emin değilim, ama bu oldukça havalı. - Blender

Jeneratör ifade protokolünden bazı özellikleri araştırdıklarını düşünüyordum, ama bu dosyaya baktıklarını ve ilgili astmodülü gördüklerini ... Hayır, program kaynağını anında incelemiyorlar, değil mi? Sanrılama ...

@BrenBarn: Oluşturucuyu selectişlev çağrısı dışında aramaya çalışırsam sonuç şudur:

>>> 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
>>>

Görünüşe göre selectişlev çağrısını incelemek ve Python soyut sözdizimi dilbilgisi ağacını anında işlemek gibi daha gizemli sözler yapıyorlar .

Hala birisinin bunu açıkladığını görmek istiyorum, kaynak büyücülük seviyemin çok ötesinde.


Muhtemelen pnesne, Pony tarafından uygulanan ve üzerinde hangi yöntemlere / özelliklere erişildiğine (örneğin name, startswith) bakan ve bunları SQL'e dönüştüren türde bir nesnedir .
BrenBarn

3
İşte peşinde olduğunuz dosya. Bazı iç gözlem sihirbazlığı kullanarak jeneratörü yeniden inşa ediyor gibi görünüyor. Python'un sözdiziminin% 100'ünü destekleyip desteklemediğinden emin değilim, ama bu oldukça havalı.
Blender

1
@Blender: LISP'de bu tür bir numara gördüm - Python'da bu dublör yapmak çok saçma!
Paulo Scardine

Yanıtlar:


209

Pony ORM yazarı burada.

Pony, Python oluşturucuyu üç adımda SQL sorgusuna çevirir:

  1. Oluşturucu bayt kodunun derlenmesi ve oluşturucu AST'nin yeniden oluşturulması (soyut sözdizimi ağacı)
  2. Python AST'nin "soyut SQL" e çevirisi - bir SQL sorgusunun evrensel liste tabanlı temsili
  3. Soyut SQL temsilini belirli bir veritabanına bağımlı SQL diyalektine dönüştürme

En karmaşık kısım, Pony'nin Python ifadelerinin "anlamını" anlaması gereken ikinci adımdır. Görünüşe göre en çok ilk adımla ilgileniyorsunuz, bu yüzden kod çözmenin nasıl çalıştığını açıklamama izin verin.

Bu sorguyu ele alalım:

>>> from pony.orm.examples.estore import *
>>> select(c for c in Customer if c.country == 'USA').show()

Hangisi aşağıdaki SQL'e çevrilecektir:

SELECT "c"."id", "c"."email", "c"."password", "c"."name", "c"."country", "c"."address"
FROM "Customer" "c"
WHERE "c"."country" = 'USA'

Aşağıda, yazdırılacak olan bu sorgunun sonucu yer almaktadır:

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()Fonksiyon argüman olarak bir piton jeneratör kabul eder ve daha sonra onun baytkod analiz eder. Standart python dismodülünü kullanarak bu jeneratörün bayt kodu talimatlarını alabiliriz :

>>> 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

Pony ORM, bayt kodundan bir AST'yi geri yükleyebilen decompile()modül içinde işleve sahiptir pony.orm.decompiling:

>>> from pony.orm.decompiling import decompile
>>> ast, external_names = decompile(gen)

Burada, AST düğümlerinin metinsel temsilini görebiliriz:

>>> ast
GenExpr(GenExprInner(Name('c'), [GenExprFor(AssName('c', 'OP_ASSIGN'), Name('.0'),
[GenExprIf(Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))]))])]))

Şimdi decompile()fonksiyonun nasıl çalıştığını görelim .

decompile()İşlevi oluşturur DecompilerZiyaretçi desen uygular nesneyi. Derleyici çözücü örneği, bayt kodu talimatlarını tek tek alır. Her komut için derleyici nesnesi kendi yöntemini çağırır. Bu yöntemin adı, mevcut bayt kodu talimatının ismine eşittir.

Python bir ifade hesapladığında, hesaplamanın ara sonucunu saklayan yığını kullanır. Derleyici nesnesinin de kendi yığını vardır, ancak bu yığın, ifade hesaplamasının sonucunu değil, ifade için AST düğümünü depolar.

Bir sonraki bayt kodu talimatı için derleyici çözme yöntemi çağrıldığında, AST düğümlerini yığından alır, bunları yeni bir AST düğümünde birleştirir ve ardından bu düğümü yığının en üstüne yerleştirir.

Örneğin, alt ifadenin nasıl c.country == 'USA'hesaplandığını görelim . Karşılık gelen bayt kodu parçası:

              9 LOAD_FAST                1 (c)
             12 LOAD_ATTR                0 (country)
             15 LOAD_CONST               0 ('USA')
             18 COMPARE_OP               2 (==)

Dolayısıyla, derleyici nesnesi şunları yapar:

  1. Aramalar decompiler.LOAD_FAST('c'). Bu yöntem, Name('c')düğümü decompiler yığınının en üstüne yerleştirir.
  2. Aramalar decompiler.LOAD_ATTR('country'). Bu yöntem, Name('c')düğümü yığından alır, düğümü oluşturur Geattr(Name('c'), 'country')ve onu yığının en üstüne yerleştirir.
  3. Aramalar decompiler.LOAD_CONST('USA'). Bu yöntem, Const('USA')düğümü yığının en üstüne yerleştirir.
  4. Aramalar decompiler.COMPARE_OP('=='). Bu yöntem, yığından iki düğüm (Getattr ve Const) alır ve ardından yığının Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))]) en üstüne yerleştirir.

Tüm bayt kodu talimatları işlendikten sonra, derleme çözücü yığını, tüm jeneratör ifadesine karşılık gelen tek bir AST düğümü içerir.

Pony ORM'nin yalnızca jeneratörleri ve lambdaları yeniden derlemesi gerektiğinden, bu o kadar karmaşık değildir, çünkü bir jeneratör için komut akışı nispeten basittir - bu sadece bir grup iç içe döngüdür.

Şu anda Pony ORM, iki şey dışında tüm jeneratör talimat setini kapsar:

  1. Satır içi if ifadeler: a if b else c
  2. Bileşik karşılaştırmaları: a < b < c

Pony böyle bir ifadeyle karşılaşırsa, NotImplementedErroristisnayı ortaya çıkarır . Ancak bu durumda bile, üreteç ifadesini bir dizge olarak ileterek çalışmasını sağlayabilirsiniz. Bir üreteci dizge olarak ilettiğinizde Pony, kod çözücü modülünü kullanmaz. Bunun yerine standart Python compiler.parseişlevini kullanarak AST alır .

Umarım bu sorunuzu yanıtlar.


26
Çok başarılı: (1) Bayt kodu çözme çok hızlıdır. (2) Her sorgu karşılık gelen kod nesnesine sahip olduğundan, bu kod nesnesi bir önbellek anahtarı olarak kullanılabilir. Bu nedenle, Pony ORM her sorguyu yalnızca bir kez çevirirken, Django ve SQLAlchemy aynı sorguyu tekrar tekrar çevirmek zorundadır. (3) Pony ORM, IdentityMap desenini kullandığından, aynı işlem içindeki sorgu sonuçlarını önbelleğe alır. Yazarın Pony ORM'nin
Alexander Kozlovsky

3
Bu pypy JIT derleyicisiyle uyumlu mu?
Mzzl

2
Test etmedim, ancak bazı Reddit yorumcuları uyumlu olduğunu söylüyor: tinyurl.com/ponyorm-pypy
Alexander Kozlovsky

9
SQLAlchemy, sorgu önbelleğe alma özelliğine sahiptir ve ORM bu özelliği kapsamlı şekilde kullanır. Varsayılan olarak açık değildir, çünkü bir SQL ifadesinin yapısını, bildirildiği kaynak koddaki konuma bağlayacak bir özelliğimiz yoktur, bu da kod nesnesinin size gerçekten verdiği şeydir. Aynı sonucu elde etmek için yığın çerçeve incelemesini kullanabiliriz, ancak bu benim zevklerime göre biraz fazla karmaşık. SQL üretimi, her durumda en az kritik performans alanıdır; satırları ve defter tutma değişikliklerini getirmektir.
zzzeek

2
@ randomsurfer_123 muhtemelen hayır, onu uygulamak için biraz zamana ihtiyacımız var (belki bir hafta) ve bizim için daha önemli olan başka görevler de var.
Alexander Kozlovsky
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.