2015-08-21 19 views
5

Rất nhiều lần, tôi đã viết các truy vấn như sau:Viết chọn các chức năng để tùy chỉnh Pony

pony.orm.select(u for u in User if u.available and u.friends > 0 and ...) 

Vì vậy, tôi muốn viết phiên bản riêng của tôi về select, một sự thay thế của nó. Một cái gì đó giống như để tránh tôi viết mỗi khi phần đầu tiên của vị từ, if u.available and u.friends > 0.

Câu hỏi của tôi tổng quát hơn: làm cách nào để viết một hàm như select mà nó chấp nhận các đối số như các phương thức select hoặc count có thể chấp nhận.

Trả lời

6

Hãy xác định một thực thể User theo cách sau:

from datetime import datetime, timedelta 
from pony.orm import * 

db = Database('sqlite', ':memory:') 

class User(db.Entity): 
    username = Required(str, unique=True) 
    password = Required(str) 
    friends = Set("User", reverse='friends') # many-to-many symmetric relation 
    online = Required(bool, default=False) # if user is currently online 
    last_visit = Optional(datetime)   # last visit time 
    disabled = Required(bool, default=False) # if user is disabled by administrator 

sql_debug(True) 
db.generate_mapping(create_tables=True) 

Bây giờ chúng ta có thể xác định một số chức năng thuận tiện để lấy loại thường xuyên nhất được sử dụng của người sử dụng. Chức năng đầu tiên sẽ trở lại với những người dùng không được vô hiệu hóa bởi người quản trị:

def active_users(): 
    return User.select(lambda user: not user.disabled) 

Trong chức năng mà tôi sử dụng select phương pháp User thực thể mà chấp nhận một hàm lambda, nhưng các chức năng tương tự có thể được viết bằng toàn cầu select chức năng mà chấp nhận biểu thức trình tạo:

def active_users(): 
    return select(u for u in User if not user.disabled) 

Kết quả của hàm active_users là một đối tượng truy vấn. Bạn có thể gọi phương thức filter của đối tượng truy vấn để tạo truy vấn cụ thể hơn. Ví dụ, tôi có thể sử dụng active_users chức năng để chọn người dùng hoạt động có tên bắt đầu bằng 'A' bức thư:

users = active_users().filter(lambda user: user.name.startswith('A')) \ 
         .order_by(User.name)[:10] 

Bây giờ tôi muốn tìm người dùng truy cập các trang web trong một vài ngày qua. Tôi có thể xác định một chức năng trong đó sử dụng các truy vấn trả về từ hàm trước và tăng cường nó theo cách sau:

def recent_users(days=1): 
    return active_users().filter(lambda u: u.last_visit > datetime.now() - timedelta(days)) 

Trong ví dụ này tôi vượt qua đối số days đến chức năng và sử dụng giá trị của nó bên trong bộ lọc.

Bạn có thể xác định tập hợp các chức năng như vậy sẽ tạo thành lớp truy cập dữ liệu trong ứng dụng của bạn. Một số ví dụ khác:

def users_with_at_least_n_friends(n=1): 
    return active_users().filter(lambda u: count(u.friends) >= n) 

def online_users(): 
    return User.select(lambda u: u.online) 

def online_users_with_at_least_n_online_friends(n=1): 
    return online_users().filter(lambda u: count(f for f in u.friends if f.online) >= n) 

users = online_users_with_at_least_n_online_friends(n=10) \ 
      .order_by(User.name)[:10] 

Trong các ví dụ trên, tôi xác định các chức năng toàn cục. Một lựa chọn khác là để xác định các chức năng như một classmethods của User thực thể:

class User(db.Entity): 
    username = Required(str, unique=True) 
    ... 
    @classmethod 
    def name_starts_with(cls, prefix): 
     return cls.select(lambda user: not user.disabled 
             and user.name.startswith(prefix)) 

... 
users = User.name_starts_with('A').order_by(desc(User.last_visit))[:10] 

Nếu bạn có thể muốn có một hàm tổng quát có thể được áp dụng cho các lớp thực thể khác nhau, sau đó bạn cần phải vượt qua lớp thực thể như một tham số. Ví dụ, nếu một số lớp khác nhau có những thuộc tính deleted, và bạn muốn có một phương pháp tổng quát để chỉ chọn đối tượng không bị xóa, bạn có thể viết một cái gì đó như thế:

def select_active(cls): 
    return cls.select(lambda obj: not obj.deleted) 

select_active(Message).filter(lambda msg: msg.author == current_user) 

Tất cả các chức năng cung cấp ở trên có một nhược điểm - chúng không phải là composable. Bạn không thể nhận được một truy vấn từ một hàm và tăng thêm nó với một hàm khác. Nếu bạn muốn có một hàm có thể tăng thêm truy vấn hiện có, hàm đó sẽ chấp nhận truy vấn như một đối số.Ví dụ:

def active_users(): 
    return User.select(lambda user: not user.disabled) 

def name_starts_with(query, prefix): 
    return query.filter(lambda user: user.name.startswith('prefix')) 

Các name_starts_with chức năng có thể được áp dụng cho truy vấn khác:

users1 = name_starts_with(active_users(), 'A').order_by(User.last_visited) 
users2 = name_starts_with(recent_users(), 'B').filter(lambda user: user.online) 

Ngoài ra chúng tôi đang làm việc trên các API mở rộng truy vấn mà sẽ cho phép một lập trình viên để viết các phương pháp truy vấn tùy chỉnh. Khi chúng tôi phát hành API này, bạn chỉ có thể kết hợp các phương thức truy vấn tùy chỉnh theo cách sau:

select(u for u in User).recent(days=3).name_starts_with('A')[:10] 

Hy vọng tôi đã trả lời câu hỏi của bạn. Nếu đúng như vậy, vui lòng chấp nhận câu trả lời đúng.

Các vấn đề liên quan