2015-08-21 9 views
5

Wiele razy pisałem zapytań tak:Pisanie wybór funkcji dla wybranego produktu Kucyk

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

Więc chciałbym napisać własną wersję select alternatywny to. Coś jak uniknąć pisania za każdym razem, gdy pierwsza część orzeczenia, if u.available and u.friends > 0.

Moje pytanie jest bardziej ogólne: w jaki sposób mogę napisać funkcję podobną do select, która akceptuje argumenty takie jak te, które może zaakceptować metoda select lub metoda count.

Odpowiedz

6

Zdefiniujmy podmiot User w następujący sposób:

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) 

Teraz możemy zdefiniować kilka wygodnych funkcji do pobierania najczęściej używanych typów użytkowników. Pierwsza funkcja zwróci użytkownikom, którzy nie są wyłączone przez administratora:

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

W tej funkcji używam select metoda User podmiotowi, który przyjmuje funkcję lambda, ale ta sama funkcja może być napisane przy użyciu globalnej select funkcji, która przyjmuje wyrażenie generatora:

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

wynikiem active_users funkcji jest przedmiotem zapytania. Możesz zadzwonić pod metodę filter obiektu zapytania, aby uzyskać bardziej szczegółowe zapytanie. Na przykład, można użyć funkcji wybierz active_users aktywnych użytkowników, których nazwy zaczynają się na literę „A”:

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

Teraz chcę znaleźć użytkowników, którzy odwiedzają stronę w ciągu kilku ostatnich dni. Można zdefiniować inną funkcję, która wykorzystuje zapytanie zwrócony od poprzedniej funkcji i zwiększa się w następujący sposób:

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

W tym przykładzie przechodzą days argument funkcji i użyć jej wartości w filtrze.

Można zdefiniować zestaw takich funkcji, które utworzą warstwę dostępu do danych aplikacji. Kilka innych przykładów:

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] 

W powyższych przykładach definiuję funkcje globalne. Inną opcją jest określenie tych funkcji jako classmethods jednostki 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] 

Jeśli możesz mieć funkcję rodzajowe, które mogą być stosowane do różnych klas encji, wtedy trzeba zdać klasę encji jako parametr. Na przykład, jeśli pewna liczba różnych klas mają atrybut deleted, a chcesz mieć ogólną metodę wybrać tylko nieusuniętych obiektów, można napisać coś takiego:

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

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

Wszystkie funkcje przewidziane powyżej mają jeden wada - nie można ich składać. Nie można uzyskać zapytania z jednej funkcji i rozszerzyć go o inną funkcję. Jeśli chcesz mieć funkcję, która może rozszerzyć istniejące zapytanie, funkcja ta powinna przyjąć zapytanie jako argument.Przykład:

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

Funkcja name_starts_with mogą być stosowane do innego zapytania:

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

Również my pracujemy nad rozszerzeniem kwerendy API, który pozwoli programista napisać niestandardowych metod zapytania. Kiedy udostępnimy ten interfejs API, będzie można po prostu łączyć niestandardowe metody zapytań w następujący sposób:

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

Mam nadzieję, że odpowiedziałem na twoje pytanie. W takim przypadku proszę przyjąć odpowiedź jako poprawną.

Powiązane problemy