2015-08-21 5 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 метод User субъекта, который принимает функцию лямбды, но та же функция может быть записана с использованием глобальной select функции, которая принимает выражение генератора:

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

результат active_users функции является объектом запроса. Вы можете вызвать метод filter объекта запроса для получения более конкретного запроса. Например, я могу использовать active_users функцию для выбора активных пользователей, чьи имена начинаются с «А» письма:

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] 

Надежда Я ответил на ваш вопрос. Если это так, пожалуйста, примите ответ как правильный.