2015-08-21 69 views
5

很多時候,我寫了類似下面的查詢:寫入選擇功能定製小馬

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

所以,我想我自己寫的版本select,它的一個替代品。有些東西想避免我每次寫謂詞的第一部分,即if u.available and u.friends > 0

我的問題是更一般的:我如何編寫一個像select這樣的函數,它接受類似select方法或count方法可以接受的參數。

回答

6

讓我們以下面的方式定義User實體:

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) 

現在我們可以定義一些方便的功能來獲取最常用的類型的用戶。第一個函數將返回誰沒有被管理員停用用戶:

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

在該功能我用select方法,它接受一個lambda函數User實體的,但可以使用全球select函數寫相同功能,接受一個發電機表達式:

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

active_users函數的結果是查詢的對象。您可以調用filter查詢對象的方法來產生更具體的查詢。例如,我可以用active_users功能來選擇活躍用戶的名字開始與「A」字母:

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

現在我想找個誰參觀了幾個最後的日子裏該網站的用戶。我可以定義一個使用從先前的函數返回的查詢另一個函數和以下列方式增加它:

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

在這個例子中我通過days參數的功能和使用的過濾器內部的價值。

您可以定義一組這樣的函數,它們將形成應用程序的數據訪問層。一些更多的例子:

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] 

在上面的例子中,我定義了全局函數。另一種選擇是定義這些功能作爲User實體的classmethods:

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] 

如果你可能希望有哪些可以適用於不同的實體類的通用功能,那麼你就需要通過實體類作爲參數。例如,如果許多不同類的有deleted屬性,你想有一個通用的方法來選擇唯一的非刪除的對象,你可以寫類似的東西:上面

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

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

提供的所有功能都有一個缺點 - 它們不可組合。您無法從一個函數獲取查詢並使用另一個函數對其進行擴充。如果你想有一個可以增加現有查詢的函數,該函數應該接受查詢作爲參數。例如:

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

name_starts_with功能可以應用到其他查詢:

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

此外,我們正在處理的查詢擴展API,這將允許程序員編寫自定義的查詢方法。當我們發佈這個API將有可能只是連鎖自定義查詢方法一起以下列方式:

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

希望我回答你的問題。如果是這種情況,請接受答案爲正確答案。