Pony (ORM) มีลูกเล่นอย่างไร?


111

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 มีการวิปัสสนาและ metaprogramming ที่ยอดเยี่ยมในตัว แต่ไลบรารีนี้สามารถแปลนิพจน์ตัวสร้างโดยไม่ต้องประมวลผลล่วงหน้าได้อย่างไร ดูเหมือนเวทมนตร์

[อัปเดต]

Blender เขียนว่า:

นี่คือไฟล์ที่คุณต้องการ ดูเหมือนว่าจะสร้างเครื่องกำเนิดไฟฟ้าขึ้นมาใหม่โดยใช้วิซาร์ดการวิปัสสนา ฉันไม่แน่ใจว่ามันรองรับไวยากรณ์ของ Python ได้ 100% หรือเปล่า แต่มันค่อนข้างเจ๋ง - เครื่องปั่น

ฉันคิดว่าพวกเขากำลังสำรวจคุณสมบัติบางอย่างจากโปรโตคอลการแสดงออกของเครื่องกำเนิดไฟฟ้า แต่กำลังมองหาไฟล์นี้และเห็นastโมดูลที่เกี่ยวข้อง ... ไม่พวกเขาไม่ได้ตรวจสอบแหล่งที่มาของโปรแกรมทันทีใช่ไหม ตะลึง...

@ เบรนบาร์น: ถ้าฉันพยายามเรียกเครื่องกำเนิดไฟฟ้านอกการ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
BrenBarn

3
นี่คือไฟล์ที่คุณต้องการ ดูเหมือนว่าจะสร้างเครื่องกำเนิดไฟฟ้าขึ้นมาใหม่โดยใช้วิซาร์ดการวิปัสสนา ฉันไม่แน่ใจว่ามันรองรับไวยากรณ์ของ Python ได้ 100% หรือเปล่า แต่มันค่อนข้างเจ๋ง
Blender

1
@Blender: ฉันเคยเห็นเคล็ดลับแบบนี้ใน LISP - การดึงความสามารถใน Python นี้เป็นเรื่องที่ไม่ดีเลย!
Paulo Scardine

คำตอบ:


209

ผู้เขียน Pony ORM อยู่ที่นี่

Pony แปล Python Generator เป็น SQL query ในสามขั้นตอน:

  1. การแยกตัวสร้าง bytecode ของเครื่องกำเนิดและการสร้าง AST (โครงสร้างไวยากรณ์แบบนามธรรม)
  2. การแปล Python AST เป็น "abstract SQL" - การแทนคำสั่ง SQL แบบรายการสากล
  3. การแปลงการแสดง SQL แบบนามธรรมให้เป็นภาษาถิ่น SQL ที่ขึ้นอยู่กับฐานข้อมูล

ส่วนที่ซับซ้อนที่สุดคือขั้นตอนที่สองซึ่ง Pony ต้องเข้าใจ "ความหมาย" ของนิพจน์ 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()ฟังก์ชั่นยอมรับกำเนิดหลามเป็นอาร์กิวเมนต์แล้ววิเคราะห์ bytecode ของมัน เราสามารถรับคำแนะนำ bytecode ของเครื่องกำเนิดไฟฟ้านี้โดยใช้disโมดูล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

Pony ORM มีฟังก์ชันdecompile()ภายในโมดูลpony.orm.decompilingซึ่งสามารถเรียกคืน AST จาก bytecode:

>>> 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()ฟังก์ชั่นทำงานอย่างไร

decompile()ฟังก์ชั่นสร้างDecompilerวัตถุซึ่งดำเนินรูปแบบของผู้เข้าชม อินสแตนซ์ตัวถอดรหัสจะได้รับคำสั่ง bytecode ทีละรายการ สำหรับแต่ละคำสั่งอ็อบเจ็กต์ decompiler จะเรียกใช้เมธอดของตัวเอง ชื่อของเมธอดนี้เท่ากับชื่อของคำสั่ง bytecode ปัจจุบัน

เมื่อ Python คำนวณนิพจน์จะใช้ stack ซึ่งเก็บผลการคำนวณระดับกลาง อ็อบเจ็กต์ decompiler ยังมีสแต็กของตัวเอง แต่สแต็กนี้ไม่เก็บผลลัพธ์ของการคำนวณนิพจน์ แต่เป็นโหนด AST สำหรับนิพจน์

เมื่อมีการเรียกวิธีการถอดรหัสสำหรับคำสั่ง bytecode ถัดไปจะใช้โหนด AST จากสแต็กรวมเข้ากับโหนด AST ใหม่จากนั้นวางโหนดนี้ไว้ที่ด้านบนสุดของสแต็ก

ตัวอย่างเช่นเรามาดูวิธีc.country == 'USA'คำนวณนิพจน์ย่อย ส่วน bytecode ที่เกี่ยวข้องคือ:

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

ดังนั้นวัตถุตัวถอดรหัสจะทำสิ่งต่อไปนี้:

  1. โทรdecompiler.LOAD_FAST('c'). วิธีนี้ทำให้ไฟล์Name('c')โหนดอยู่ด้านบนของสแต็กถอดรหัส
  2. โทรdecompiler.LOAD_ATTR('country'). วิธีนี้นำName('c')โหนดจากสแต็กสร้างไฟล์Geattr(Name('c'), 'country')โหนดและวางไว้ที่ด้านบนสุดของสแต็ก
  3. โทรdecompiler.LOAD_CONST('USA'). วิธีนี้ทำให้ไฟล์Const('USA')โหนดอยู่ด้านบนของสแต็ก
  4. โทรdecompiler.COMPARE_OP('=='). วิธีนี้ใช้สองโหนด (Getattr และ Const) จากสแต็กจากนั้นวางCompare(Getattr(Name('c'), 'country'), [('==', Const('USA'))]) ไว้ที่ด้านบนสุดของสแต็ก

หลังจากประมวลผลคำสั่ง bytecode ทั้งหมดแล้ว decompiler stack จะมีโหนด AST เดียวซึ่งสอดคล้องกับนิพจน์ตัวสร้างทั้งหมด

เนื่องจาก Pony ORM จำเป็นต้องแยกเครื่องกำเนิดไฟฟ้าและแลมบ์ดาสเท่านั้นสิ่งนี้จึงไม่ซับซ้อนนักเนื่องจากขั้นตอนการเรียนการสอนสำหรับเครื่องกำเนิดไฟฟ้านั้นค่อนข้างตรงไปตรงมามันเป็นเพียงการวนซ้ำที่ซ้อนกัน

ปัจจุบัน Pony ORM ครอบคลุมชุดคำสั่งของเครื่องกำเนิดไฟฟ้าทั้งหมดยกเว้นสองสิ่ง:

  1. อินไลน์ if นิพจน์: a if b else c
  2. การเปรียบเทียบเชิงเปรียบเทียบ: a < b < c

หาก Pony พบการแสดงออกดังกล่าวจะทำให้เกิดNotImplementedErrorข้อยกเว้น แต่ในกรณีนี้คุณสามารถทำให้มันทำงานได้โดยส่งนิพจน์ตัวสร้างเป็นสตริง เมื่อคุณส่งเครื่องกำเนิดไฟฟ้าเป็นสตริง Pony จะไม่ใช้โมดูลถอดรหัส แต่จะได้รับ AST โดยใช้ Python มาตรฐานcompiler.parseฟังก์ชัน

หวังว่านี่จะตอบคำถามของคุณ


26
มีประสิทธิภาพมาก: (1) การถอดรหัส Bytecode นั้นเร็วมาก (2) เนื่องจากแบบสอบถามแต่ละรายการมีวัตถุรหัสที่ตรงกันจึงสามารถใช้วัตถุรหัสนี้เป็นคีย์แคชได้ ด้วยเหตุนี้ Pony ORM จึงแปลแต่ละแบบสอบถามเพียงครั้งเดียวในขณะที่ Django และ SQLAlchemy ต้องแปลคำค้นหาเดียวกันซ้ำแล้วซ้ำเล่า (3) เนื่องจาก Pony ORM ใช้รูปแบบ IdentityMap จึงเก็บผลลัพธ์การค้นหาไว้ในธุรกรรมเดียวกัน มีโพสต์ (ในรัสเซีย) ที่ผู้เขียนระบุว่า Pony ORM เร็วกว่า Django และ SQLAlchemy 1.5-3 เท่าแม้ว่าจะไม่มีการแคชผลการสืบค้น: habrahabr.ru/post/188842
Alexander Kozlovsky

3
เข้ากันได้กับคอมไพเลอร์ pypy JIT หรือไม่
Mzzl

2
ฉันไม่ได้ทดสอบ แต่ผู้วิจารณ์ Reddit บางคนบอกว่าเข้ากันได้: tinyurl.com/ponyorm-pypy
Alexander Kozlovsky

9
SQLAlchemy มีการแคชแบบสอบถามและ ORM ใช้ประโยชน์จากคุณลักษณะนี้อย่างกว้างขวาง ไม่ได้เปิดโดยค่าเริ่มต้นเนื่องจากเป็นความจริงเราไม่มีคุณลักษณะที่จะเชื่อมโยงการสร้างนิพจน์ SQL กับตำแหน่งในซอร์สโค้ดที่มีการประกาศซึ่งเป็นสิ่งที่ออบเจ็กต์รหัสมอบให้คุณจริงๆ เราสามารถใช้การตรวจสอบสแต็กเฟรมเพื่อให้ได้ผลลัพธ์แบบเดียวกัน แต่นั่นเป็นเพียงการแฮ็คเล็กน้อยสำหรับรสนิยมของฉัน การสร้าง SQL เป็นพื้นที่ประสิทธิภาพที่สำคัญน้อยที่สุดในทุกกรณี การเรียกแถวและการเปลี่ยนแปลงการทำบัญชีคือ
zzzeek

2
@ randomsurfer_123 อาจจะไม่เราแค่ต้องการเวลาในการใช้งาน (อาจจะเป็นสัปดาห์) และยังมีงานอื่น ๆ ที่สำคัญกว่าสำหรับเรา
Alexander Kozlovsky
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.