【问题标题】:Writing select functions for customized Pony为自定义 Pony 编写选择函数
【发布时间】:2015-08-21 01:15:39
【问题描述】:

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

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

【问题讨论】:

    标签: python python-2.7 ponyorm


    【解决方案1】:

    让我们用以下方式定义一个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)
    

    在该函数中,我使用User 实体的select 方法,它接受一个lambda 函数,但同样的函数可以使用接受生成器表达式的全局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]
    

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

    【讨论】:

      猜你喜欢
      • 2019-03-14
      • 2021-03-09
      • 2023-03-27
      • 1970-01-01
      • 2021-04-19
      • 2011-06-11
      • 2021-03-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多