Làm thế nào Pony (ORM) thực hiện các thủ thuật của nó?


111

Pony ORM thực hiện một thủ thuật hay là chuyển đổi một biểu thức trình tạo thành SQL. Thí dụ:

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

Tôi biết Python có nội dung lập trình nội quan và lập trình ẩn tuyệt vời, nhưng làm thế nào thư viện này có thể dịch biểu thức trình tạo mà không cần xử lý trước? Nó trông giống như ma thuật.

[cập nhật]

Máy xay sinh tố đã viết:

Đây là tệp mà bạn đang theo dõi. Nó dường như tái tạo lại bộ tạo bằng cách sử dụng một số thuật sĩ nội quan. Tôi không chắc liệu nó có hỗ trợ 100% cú pháp của Python hay không, nhưng điều này khá tuyệt. - Máy xay

Tôi đã nghĩ rằng họ đang khám phá một số tính năng từ giao thức biểu thức trình tạo, nhưng nhìn vào tệp này và thấy astmô-đun có liên quan ... Không, họ không kiểm tra nguồn chương trình một cách nhanh chóng, phải không? Tâm trí ...

@BrenBarn: Nếu tôi cố gắng gọi trình tạo bên ngoài lệnh selectgọi hàm, kết quả là:

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

Có vẻ như họ đang làm những câu thần chú phức tạp hơn như kiểm tra select gọi hàm và xử lý nhanh cây ngữ pháp cú pháp trừu tượng Python.

Tôi vẫn muốn xem ai đó giải thích nó, nguồn này vượt quá trình độ thuật sĩ của tôi.


Có lẽ pđối tượng là một đối tượng của một kiểu do Pony thực hiện, nó sẽ xem xét những phương thức / thuộc tính nào đang được truy cập trên nó (ví dụ name, startswith) và chuyển đổi chúng sang SQL.
BrenBarn

3
Đây là tệp mà bạn đang theo dõi. Nó dường như tái tạo lại bộ tạo bằng cách sử dụng một số thuật sĩ nội quan. Tôi không chắc liệu nó có hỗ trợ 100% cú pháp của Python hay không, nhưng điều này khá tuyệt.
Máy xay sinh tố

1
@Blender: Tôi đã thấy loại thủ thuật này trong LISP - kéo thế này bằng Python chỉ đơn giản là bệnh hoạn!
Paulo Scardine

Câu trả lời:


209

Tác giả Pony ORM ở đây.

Pony dịch trình tạo Python thành truy vấn SQL trong ba bước:

  1. Giải mã bytecode của trình tạo và xây dựng lại trình tạo AST (cây cú pháp trừu tượng)
  2. Bản dịch AST của Python thành "SQL trừu tượng" - đại diện dựa trên danh sách phổ quát của một truy vấn SQL
  3. Chuyển đổi biểu diễn SQL trừu tượng thành phương ngữ SQL phụ thuộc vào cơ sở dữ liệu cụ thể

Phần phức tạp nhất là bước thứ hai, nơi Pony phải hiểu "ý nghĩa" của các biểu thức Python. Có vẻ như bạn quan tâm nhất đến bước đầu tiên, vì vậy hãy để tôi giải thích cách dịch ngược hoạt động.

Hãy xem xét truy vấn này:

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

Cái nào sẽ được dịch sang SQL sau:

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

Và dưới đây là kết quả của truy vấn này sẽ được in ra:

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

Các select()chức năng chấp nhận một máy phát điện python như là đối số, và sau đó phân tích bytecode của nó. Chúng tôi có thể nhận hướng dẫn bytecode của trình tạo này bằng cách sử dụng dismô-đun python tiêu chuẩn :

>>> 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 có chức năng decompile()trong mô-đun pony.orm.decompilingcó thể khôi phục AST từ mã bytecode:

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

Ở đây, chúng ta có thể thấy biểu diễn dạng văn bản của các nút AST:

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

Bây giờ chúng ta hãy xem làm thế nào decompile() chức năng hoạt động .

Các decompile()chức năng tạo ra mộtDecompiler đối tượng, mà thực hiện mô hình của khách. Cá thể trình dịch ngược nhận được các hướng dẫn bytecode từng cái một. Đối với mỗi lệnh, đối tượng trình dịch ngược gọi phương thức riêng của nó. Tên của phương thức này giống với tên của lệnh bytecode hiện tại.

Khi Python tính toán một biểu thức, nó sử dụng ngăn xếp để lưu trữ kết quả trung gian của phép tính. Đối tượng trình dịch ngược cũng có ngăn xếp riêng của nó, nhưng ngăn xếp này không lưu trữ kết quả của phép tính biểu thức, mà là nút AST cho biểu thức.

Khi phương thức dịch ngược cho lệnh bytecode tiếp theo được gọi, nó lấy các nút AST từ ngăn xếp, kết hợp chúng thành một nút AST mới, rồi đặt nút này lên trên cùng của ngăn xếp.

Ví dụ, hãy xem cách tính biểu thức con c.country == 'USA'. Đoạn mã bytecode tương ứng là:

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

Vì vậy, đối tượng dịch ngược thực hiện những việc sau:

  1. Cuộc gọi decompiler.LOAD_FAST('c'). Phương pháp này đặtName('c') nút ở trên cùng của ngăn xếp trình dịch ngược.
  2. Cuộc gọi decompiler.LOAD_ATTR('country'). Phương thức này lấy Name('c')nút từ ngăn xếp, tạoGeattr(Name('c'), 'country') nút và đặt nó lên trên cùng của ngăn xếp.
  3. Cuộc gọi decompiler.LOAD_CONST('USA'). Phương pháp này đặtConst('USA') nút ở trên cùng của ngăn xếp.
  4. Cuộc gọi decompiler.COMPARE_OP('=='). Phương thức này lấy hai nút (Getattr và Const) từ ngăn xếp, sau đó đặt Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))]) lên trên cùng của ngăn xếp.

Sau khi tất cả các lệnh bytecode được xử lý, ngăn xếp trình dịch ngược chứa một nút AST duy nhất tương ứng với toàn bộ biểu thức trình tạo.

Vì Pony ORM chỉ cần dịch ngược bộ tạo và lambdas, điều này không phức tạp lắm, bởi vì luồng lệnh cho bộ tạo tương đối đơn giản - nó chỉ là một loạt các vòng lặp lồng nhau.

Hiện tại Pony ORM bao gồm toàn bộ bộ hướng dẫn của trình tạo ngoại trừ hai điều:

  1. Các biểu thức if nội dòng: a if b else c
  2. So sánh phức hợp: a < b < c

Nếu Pony gặp phải biểu hiện như vậy, nó sẽ dẫn đến NotImplementedErrorngoại lệ. Nhưng ngay cả trong trường hợp này, bạn có thể làm cho nó hoạt động bằng cách chuyển biểu thức trình tạo dưới dạng một chuỗi. Khi bạn truyền một trình tạo dưới dạng chuỗi, Pony không sử dụng mô-đun dịch ngược. Thay vào đó, nó nhận được AST bằng cách sử dụng Python tiêu chuẩncompiler.parse hàm .

Hy vọng điều này trả lời câu hỏi của bạn.


26
Rất hiệu quả: (1) Dịch ngược byte rất nhanh. (2) Vì mỗi truy vấn có đối tượng mã tương ứng, đối tượng mã này có thể được sử dụng làm khóa bộ nhớ cache. Do đó, Pony ORM chỉ dịch mỗi truy vấn một lần, trong khi Django và SQLAlchemy phải dịch đi dịch lại cùng một truy vấn. (3) Vì Pony ORM sử dụng mẫu IdentityMap nên nó lưu vào bộ nhớ cache các kết quả truy vấn trong cùng một giao dịch. Có một bài (bằng tiếng Nga), nơi tác giả tiểu bang mà Pony ORM hóa ra là nhanh hơn so với Django và SQLAlchemy 1,5-3 lần ngay cả khi không kết quả truy vấn bộ nhớ đệm: habrahabr.ru/post/188842
Alexander Kozlovsky

3
Điều này có tương thích với trình biên dịch pypy JIT không?
Mzzl

2
Tôi không thử nghiệm nó, nhưng một số commenter Reddit nói nó là tương thích: tinyurl.com/ponyorm-pypy
Alexander Kozlovsky

9
SQLAlchemy có bộ nhớ đệm truy vấn và ORM sử dụng rộng rãi tính năng này. Nó không được bật theo mặc định vì đúng là chúng tôi không có sẵn tính năng để liên kết việc xây dựng biểu thức SQL với vị trí trong mã nguồn mà nó được khai báo, đó là những gì đối tượng mã thực sự mang lại cho bạn. Chúng tôi có thể sử dụng kiểm tra khung ngăn xếp để có được kết quả tương tự nhưng điều đó hơi quá khó so với sở thích của tôi. Thế hệ SQL là lĩnh vực hiệu suất ít quan trọng nhất trong mọi trường hợp; tìm nạp các hàng và thay đổi sổ sách kế toán là.
zzzeek

2
@ randomsurfer_123 có thể là không, chúng tôi chỉ cần một chút thời gian để triển khai nó (có thể là một tuần) và có những nhiệm vụ khác quan trọng hơn đối với chúng tôi.
Alexander Kozlovsky
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.