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ような引数を受け入れるような関数をどのように書くことができますか。 selectcount

4

1 に答える 1

9

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のメソッドを使用しますが、ジェネレーター式を受け入れるグローバル関数を使用して同じ関数を作成できます。Userselect

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]

上記の例では、グローバル関数を定義しています。もう 1 つのオプションは、これらの関数を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)

上記のすべての関数には、1 つの欠点があります。それらは構成可能ではありません。ある関数からクエリを取得して、別の関数で拡張することはできません。既存のクエリを拡張できる関数が必要な場合、その関数はクエリを引数として受け入れる必要があります。例:

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]

あなたの質問に答えていただければ幸いです。この場合、答えを正しいものとして受け入れてください。

于 2015-08-21T13:30:33.267 に答える