私は何人かの同僚とこれについて議論していました。 Djangoでオブジェクトを1つだけ期待しているときにオブジェクトを取得する好ましい方法はありますか?
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つ目はもっと回り道ですが、例外は発生しません。
これらのどれが望ましいかについての考えはありますか?どちらがより効率的ですか?
get()
は、この場合専用に提供されています。これを使って。
オプション2は、ほぼ正確にget()
メソッドがDjangoで実際に実装される方法です。したがって、「パフォーマンス」の違いはないはずです(そして、あなたがそれについて考えているという事実は、枢機violの1つに違反していることを示していますプログラミングのルール、つまり、コードを作成してプロファイルを作成する前にコードを最適化しようとする-コードを入手して実行できるようになるまで、コードがどのように実行されるかわからず、それ以前に最適化を試みるのは苦痛の道です) 。
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が正しいです。 Python例外はリターンと同等のオーバーヘッドを持ちます。簡略化された証拠については、 this をご覧ください。
2これがDjangoがバックエンドで行っていることです。get
はfilter
を呼び出し、アイテムが見つからない場合、または複数のオブジェクトが見つかった場合に例外を発生させます。
私はパーティーに少し遅れましたが、Django 1.6では、クエリセットにfirst()
メソッドがあります。
https://docs.djangoproject.com/en/dev/ref/models/querysets/#Django.db.models.query.QuerySet.first
クエリセットに一致する最初のオブジェクトを返します。一致するオブジェクトがない場合はNoneを返します。クエリセットに順序が定義されていない場合、クエリセットは主キーによって自動的に順序付けされます。
例:
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
なぜすべてが機能するのですか? 4行を1つの組み込みショートカットに置き換えます。 (これは独自のtry/exceptを行います。)
from Django.shortcuts import get_object_or_404
obj = get_object_or_404(MyModel, id=1)
私はDjangoの経験を話すことはできませんが、オプション#1はシステムに1つのオブジェクトを要求していることを明確に示していますが、2番目のオプションはそうではありません。特に、フィルタリングする属性が一意であることが保証されていない場合、キャッシュまたはデータベースインデックスを簡単に利用できます。
また、(再び、推測)2番目のオプションは、通常、filter()呼び出しが多くの行を返す可能性があるため、何らかの結果コレクションまたは反復子オブジェクトを作成する必要があります。 get()でこれをバイパスします。
最後に、最初のオプションはより短く、余分な一時変数を省略します-わずかな違いだけですが、少しずつ役立ちます。
例外に関する詳細情報。育てられなければ、ほとんど費用はかかりません。したがって、結果が得られる可能性があることがわかっている場合は、例外を使用します。条件式を使用すると、何があっても毎回チェックするコストがかかるためです。一方で、それらは発生したときに条件式よりも少しコストがかかるため、何らかの頻度で結果が得られないと予想する場合(たとえば、メモリが提供される場合、時間の30%)、条件チェックが行われます少し安くなるように。
しかし、これはDjangoのORMであり、おそらくデータベースへのラウンドトリップ、またはキャッシュされた結果がパフォーマンス特性を支配する可能性が高いため、この場合は1つの結果のみを期待するため、読みやすさを優先して、get()
。
私はこの問題を少し試してみたところ、オプション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]
このアプローチに切り替えることで、アプリケーションが実行するクエリの数を大幅に減らすことができました。
興味深い質問ですが、私にとっては時期尚早な最適化のオプション#2の続きです。どちらがよりパフォーマンスが良いかはわかりませんが、オプション#1は確かに見た目と感じがPythonに近いです。
別のデザインをお勧めします。
可能性のある結果に対して機能を実行する場合は、次のようにQuerySetから派生できます。 http://djangosnippets.org/snippets/734/
結果はとても素晴らしいです、例えば:
_MyModel.objects.filter(id=1).yourFunction()
_
ここで、filterは空のクエリセットまたは単一のアイテムを持つクエリセットのいずれかを返します。カスタムクエリセット関数もチェーン可能で再利用可能です。すべてのエントリに対して実行する場合:MyModel.objects.all().yourFunction()
。
また、管理インターフェイスのアクションとして使用するのにも理想的です。
_def yourAction(self, request, queryset):
queryset.yourFunction()
_
オプション1はよりエレガントですが、必ずtry..exceptを使用してください。
私自身の経験から、データベースには複数の一致するオブジェクトが存在することはないと確信していることがありますが、それでも2つのオブジェクトが存在することがわかります(もちろん、オブジェクトを主キーで取得する場合を除く)。