Djangoフィルターと単一オブジェクトの取得?


147

私はこれについて何人かの同僚と議論していました。あなたが1つだけを期待しているときにDjangoでオブジェクトを取得するための好ましい方法はありますか?

2つの明白な方法は次のとおりです。

try:
    obj = MyModel.objects.get(id=1)
except MyModel.DoesNotExist:
    # We have no object! Do something...
    pass

そして:

objs = MyModel.objects.filter(id=1)

if len(objs) == 1:
    obj = objs[0]
else:
    # We have no object! Do something...
    pass

最初の方法は、動作はより正しいようですが、制御フローで例外を使用しているため、オーバーヘッドが発生する可能性があります。2つ目はより回り道ですが、例外は発生しません。

これらのうちどれが好ましいと思いますか?どちらがより効率的ですか?

回答:


177

get()このケースのために特別に提供されています。これを使って。

オプション2は、get()メソッドがDjangoで実際にどのように実装されているかを正確に示しているため、「パフォーマンス」の違いはありません(そして、それについて考えているという事実は、プログラミングの基本的なルールの1つに違反していること、つまりコードが記述およびプロファイルされる前にコードを最適化する-コードを入手して実行できるようになるまで、コードがどのように機能するかわからないため、その前に最適化を試みるのは苦痛の道です)。


すべてが正しいですが、回答にさらに情報を追加する必要がありますか?1. Pythonはtry / except(EAFPを参照)を奨励します。それが理由 QS.get()です。2.詳細:「1つだけを期待する」とは、常に0-1オブジェクトを意味しますか、それとも2つ以上のオブジェクトを持つことが可能であり、その場合も処理する必要があります(この場合len(objs)はひどい考えです)?3.ベンチマークのないオーバーヘッドについては何も仮定しないでください(この場合try/except、少なくとも呼び出しの半分が何かを返す限り、より高速になると思います)
imposeren

>つまり、コードが記述およびプロファイルされる前にコードを最適化しようとすることこれは興味深い発言です。それを実装する前に、何かを実装するための最もオプションの方法を考えるべきだといつも思っていました。それは間違っていますか?この点について詳しく説明していただけますか?これを詳細に説明するリソースはありますか?
Parth Sharma

誰もが最初に言及したことに驚いています()。他のアドバイスは、それがこのシナリオのために行われた呼び出しであることを示しているようです。stackoverflow.com/questions/5123839/…–
NeilG、

29

あなたはdjango-annoyingと呼ばれるモジュールをインストールして、これを行うことができます:

from annoying.functions import get_object_or_None

obj = get_object_or_None(MyModel, id=1)

if not obj:
    #omg the object was not found do some error stuff

1
なぜそのような方法があるのが面倒なのですか?私には元気に見えます!
トーマス

17

1が正解です。Pythonでは、例外は戻りと同じオーバーヘッドを持ちます。簡単な証明については、これを見てください。

2これはDjangoがバックエンドで行っていることです。アイテムが見つからない場合、または複数のオブジェクトが見つかった場合は、get呼び出しfilterて例外を発生させます。


1
そのテストはかなり不公平です。例外をスローする際のオーバーヘッドの大部分は、スタックトレースの処理です。このテストのスタック長は1で、アプリケーションで通常見られるよりもはるかに短くなっています。
ロブヤング

@ロブ・ヤング:どういう意味ですか?典型的な「許可ではなく許しを求める」スキームのスタックトレース処理はどこにありますか?処理時間は、例外が発生する距離ではなく、例外が発生する距離に依存します(Javaで記述せず、e.printStackTrace()を呼び出していない場合)。そして、ほとんどの場合(ディクショナリ検索のように)-例外はのすぐ下でスローされますtry
Tomasz Gandor 2013

12

パーティーには少し遅れますが、Django 1.6 first()ではクエリセットにメソッドがあります。

https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.first


クエリセットによって一致した最初のオブジェクトを返します。一致するオブジェクトがない場合はNoneを返します。QuerySetに順序付けが定義されていない場合、querysetは主キーによって自動的に順序付けされます。

例:

p = Article.objects.order_by('title', 'pub_date').first()
Note that first() is a convenience method, the following code sample is equivalent to the above example:

try:
    p = Article.objects.order_by('title', 'pub_date')[0]
except IndexError:
    p = None

クエリにオブジェクトが1つしかないことは保証されません
py_dude

8

私はDjangoの経験を話すことはできませんが、オプション#1はシステムに1つのオブジェクトを要求していることを明確に伝えますが、2番目のオプションはそうではありません。これは、オプション#1は、キャッシュまたはデータベースインデックスをより簡単に利用できることを意味します。特に、フィルタリングする属性が一意であることが保証されていない場合です。

また、(繰り返しますが)2番目のオプションでは、通常、filter()呼び出しが多くの行を返す可能性があるため、何らかの結果コレクションまたはイテレータオブジェクトを作成する必要がある場合があります。get()でこれをバイパスします。

最後に、最初のオプションはどちらも短く、余分な一時変数を省略します-わずかな違いだけですが、少しでも役立ちます。


Djangoの使用経験はありませんが、まだ問題はありません。デフォルトで明示的で簡潔で安全であることは、言語やフレームワークに関係なく、適切な原則です。
nevelis 2017

8

なぜそれがすべて機能するのですか?4行を1つの組み込みショートカットに置き換えます。(これは独自のtry / exceptを行います。)

from django.shortcuts import get_object_or_404

obj = get_object_or_404(MyModel, id=1)

1
これは、望ましい動作である場合に最適ですが、欠落しているオブジェクトを作成したい場合や、プルがオプションの情報だった場合があります。
SingleNegationElimination 2009年

2
それがModel.objects.get_or_create()目的です
ボートコーダー2014年

7

例外に関する詳細情報。彼らが育てられなければ、彼らはほとんど何の費用もかかりません。したがって、おそらく結果が得られることがわかっている場合は、例外を使用してください。条件式を使用すると、何があっても毎回チェックするコストがかかるためです。一方、条件が発生した場合、条件式よりも少しコストがかかるため、一定の頻度(たとえば、メモリが機能している場合は時間の30%)で結果が得られないと予想される場合、条件チェックは少し安くなります。

しかし、これはDjangoのORMであり、おそらくデータベースへの往復、またはキャッシュされた結果がパフォーマンス特性を支配する可能性が高いため、この場合は読みやすさを優先しますget()


4

私はこの問題を少し試しましたが、オプション2が2つのSQLクエリを実行することを発見しました。これは、このような単純なタスクでは過剰です。私の注釈を参照してください:

objs = MyModel.objects.filter(id=1) # This does not execute any SQL
if len(objs) == 1: # This executes SELECT COUNT(*) FROM XXX WHERE filter
    obj = objs[0]  # This executes SELECT x, y, z, .. FROM XXX WHERE filter
else: 
    # we have no object!  do something
    pass

単一のクエリを実行する同等のバージョンは次のとおりです。

items = [item for item in MyModel.objects.filter(id=1)] # executes SELECT x, y, z FROM XXX WHERE filter
count = len(items) # Does not execute any query, items is a standard list.
if count == 0:
   return None
return items[0]

このアプローチに切り替えることで、アプリケーションが実行するクエリの数を大幅に減らすことができました。


1

興味深い質問ですが、私にとっては、オプション#2の最適化の時期尚早です。どちらがより高性能であるかはわかりませんが、オプション#1の方がPythonのように見えて感じられます。


1

別のデザインをお勧めします。

考えられる結果に対して関数を実行したい場合は、次のようにQuerySetから派生させることができます。http//djangosnippets.org/snippets/734/

結果はかなり素晴らしいです。たとえば、次のようにできます。

MyModel.objects.filter(id=1).yourFunction()

ここで、filterは空のクエリセットまたは単一のアイテムを含むクエリセットを返します。カスタムクエリセット関数もチェーン可能で、再利用可能です。すべてのエントリに対して実行する場合:MyModel.objects.all().yourFunction()

また、管理インターフェースのアクションとして使用するのにも理想的です。

def yourAction(self, request, queryset):
    queryset.yourFunction()

0

オプション1はよりエレガントですが、必ずtry..exceptを使用してください。

私自身の経験から、データベースに一致するオブジェクトが1つしか存在しない可能性があることは確かですが、2つあることは確かです(もちろん、主キーでオブジェクトを取得する場合は除きます)。


0

この問題にもう1つテイクを追加して申し訳ありませんが、私はdjango paginatorを使用しており、データ管理アプリでは、ユーザーはクエリ対象を選択できます。それがドキュメントのIDである場合もありますが、それ以外の場合は、複数のオブジェクトを返す一般的なクエリ、つまりクエリセットです。

ユーザーがIDをクエリすると、次のように実行できます。

Record.objects.get(pk=id)

これはdjangoのページネーターでエラーをスローします。これはレコードであり、レコードのクエリセットではないためです。

実行する必要があります:

Record.objects.filter(pk=id)

これは、1つのアイテムを含むクエリセットを返します。その後、ページネーターは正常に動作します。


ページネーションまたはQuerySetを必要とする機能を使用するには、クエリがQuerySetを返す必要があります。すでに理解しているように、.filter()と.get()の使用を切り替えず、.filter()を使用して、「pk = id」フィルターを指定します。これがこのユースケースのパターンです。
Cornel Masson

0

。取得する()

指定されたルックアップパラメータに一致するオブジェクトを返します。これは、フィールドルックアップで説明されている形式である必要があります。

get()は、複数のオブジェクトが見つかった場合にMultipleObjectsReturnedを発生させます。MultipleObjectsReturned例外は、モデルクラスの属性です。

get()は、指定されたパラメーターのオブジェクトが見つからなかった場合、DoesNotExist例外を発生させます。この例外は、モデルクラスの属性でもあります。

。フィルタ()

指定された検索パラメーターに一致するオブジェクトを含む新しいQuerySetを返します。

注意

単一の一意のオブジェクトを取得する場合はget()を使用し、ルックアップパラメータに一致するすべてのオブジェクトを取得する場合はfilter()を使用します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.