为自定义Pony编写选择函数

时间:2015-08-21 01:15:39

标签: python python-2.7 ponyorm

很多时候,我写了如下的查询:

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方法可以接受的参数。

1 个答案:

答案 0 :(得分:8)

让我们通过以下方式定义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)

现在我们可以定义一些方便的函数来检索最常用的用户类型。第一个函数将返回admin未禁用的用户:

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

在该函数中,我使用接受lambda函数的select实体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实体的类方法:

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]

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