Django filter versus get for single object?

2019-01-16 02:30发布

I was having a debate on this with some colleagues. Is there a preferred way to retrieve an object in Django when you're expecting only one?

The two obvious ways are:

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

and

   objs = MyModel.objects.filter(id=1)
   if len(objs) == 1:
      obj = objs[0]
   else: 
      # we have no object!  do something
      pass

The first method seems behaviorally more correct, but uses exceptions in control flow which may introduce some overhead. The second is more roundabout but won't ever raise an exception.

Any thoughts on which of these is preferable? Which is more efficient?

11条回答
Fickle 薄情
2楼-- · 2019-01-16 02:40

I'm a bit late to the party, but with Django 1.6 there is the first() method on querysets.

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


Returns the first object matched by the queryset, or None if there is no matching object. If the QuerySet has no ordering defined, then the queryset is automatically ordered by the primary key.

Example:

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
查看更多
聊天终结者
3楼-- · 2019-01-16 02:45

get() is provided specifically for this case. Use it.

Option 2 is almost precisely how the get() method is actually implemented in Django, so there should be no "performance" difference (and the fact that you're thinking about it indicates you're violating one of the cardinal rules of programming, namely trying to optimize code before it's even been written and profiled -- until you have the code and can run it, you don't know how it will perform, and trying to optimize before then is a path of pain).

查看更多
甜甜的少女心
4楼-- · 2019-01-16 02:46

Some more info about exceptions. If they are not raised, they cost almost nothing. Thus if you know you are probably going to have a result, use the exception, since using a conditional expression you pay the cost of checking every time, no matter what. On the other hand, they cost a bit more than a conditional expression when they are raised, so if you expect not to have a result with some frequency (say, 30% of the time, if memory serves), the conditional check turns out to be a bit cheaper.

But this is Django's ORM, and probably the round-trip to the database, or even a cached result, is likely to dominate the performance characteristics, so favor readability, in this case, since you expect exactly one result, use get().

查看更多
等我变得足够好
5楼-- · 2019-01-16 02:46

Interesting question, but for me option #2 reeks of premature optimisation. I'm not sure which is more performant, but option #1 certainly looks and feels more pythonic to me.

查看更多
爱情/是我丢掉的垃圾
6楼-- · 2019-01-16 02:48

I've played with this problem a bit and discovered that the option 2 executes two SQL queries, which for such a simple task is excessive. See my annotation:

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

An equivalent version that executes a single query is:

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]

By switching to this approach, I was able to substantially reduce number of queries my application executes.

查看更多
甜甜的少女心
7楼-- · 2019-01-16 02:53

1 is correct. In Python an exception has equal overhead to a return. For a simplified proof you can look at this.

2 This is what Django is doing in the backend. get calls filter and raises an exception if no item is found or if more than one object is found.

查看更多
登录 后发表回答