2015-08-21 38 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] 

希望我回答你的问题。如果是这种情况,请接受答案为正确答案。