2009-06-19 1 views
108

У меня были дебаты об этом с некоторыми коллегами. Есть ли предпочтительный способ получить объект в Django, если вы ожидаете только одного?Фильтр Django против получения для одного объекта?

Два очевидных способа являются:

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 

Первый метод кажется поведенчески более правильным, но использует исключения в потоке управления, который может ввести некоторые накладные расходы. Второй - более круговой, но никогда не будет создавать исключение.

Любые мысли, какие из них предпочтительнее? Что более эффективно?

ответ

137

get() предоставляется специально для этого случая. Используй это.

Вариант 2 - это почти точно, как метод get() фактически реализован в Django, поэтому не должно быть разницы в производительности (и тот факт, что вы думаете об этом, указывает на то, что вы нарушаете одно из основных правил программирование, а именно пытаться оптимизировать код до того, как он даже был написан и профилирован - пока вы не получите код и не сможете его запустить, вы не знаете, как он будет работать, а попытка оптимизировать до этого - это путь боли).

1

Интересный вопрос, но для меня вариант № 2 воняет преждевременной оптимизации. Я не уверен, что более впечатляюще, но вариант № 1, безусловно, выглядит и чувствует себя более питоническим для меня.

6

Я не могу говорить с каким-либо опытом Django, но опция №1 четко сообщает системе, что вы запрашиваете 1 объект, тогда как второй вариант этого не делает. Это означает, что опция № 1 может более легко использовать преимущества индексов кеша или базы данных, особенно если атрибут, на который вы фильтруете, не гарантированно уникален.

Также (опять же, спекулируя) второй вариант, возможно, должен создать какую-то коллекцию результатов или объект-итератор, так как вызов filter() может нормально возвращать много строк. Вы обойдете это с помощью get().

И, наконец, первый вариант является более коротким и пропускает дополнительную временную переменную - только незначительная разница, но каждый помогает.

+0

Нет опыта с Django, но по-прежнему на месте. Явно, красно и безопасно по умолчанию, являются хорошими принципами независимо от языка или структуры. – nevelis

13

1 правильный. В Python исключение имеет равные накладные расходы на возврат. Для упрощенного доказательства вы можете посмотреть this.

2 Это то, что Django делает в бэкэнде. get вызывает filter и вызывает исключение, если элемент не найден или обнаружено более одного объекта.

+1

Этот тест довольно несправедлив. Большая часть накладных расходов при бросании исключения - это обработка трассировки стека. Этот тест имел длину стека 1, которая намного ниже, чем вы обычно находили в приложении. –

+0

@ Rob Young: Что ты имеешь в виду? Где вы видите обработку трассировки стека в типичной схеме «просить прощения, а не разрешения»? Время обработки зависит от расстояния, в котором происходит исключение, а не от того, насколько глубоко это происходит (когда мы не пишем в java и не вызываем e.printStackTrace()). И чаще всего (например, в поиске словаря) - исключение вызывается чуть ниже 'try'. –

5

Зачем все это работает? Замените 4 строки с помощью 1 встроенного ярлыка. (Это делает свою попытку/исключение.)

from django.shortcuts import get_object_or_404 

obj = get_object_or_404(MyModel, id=1) 
+1

Это замечательно, когда это желаемое поведение, но иногда вы можете захотеть создать отсутствующий объект, или pull был необязательной информацией. – SingleNegationElimination

+1

Это то, что 'Model.objects.get_or_create()' для – boatcoder

6

Дополнительная информация об исключениях. Если они не подняты, они почти ничего не стоят. Таким образом, если вы знаете, что у вас, вероятно, будет результат, используйте исключение, поскольку с помощью условного выражения вы платите стоимость проверки каждый раз, независимо от того, что. С другой стороны, они стоят немного больше, чем условное выражение, когда они поднимаются, поэтому, если вы ожидаете, что не получите результат с некоторой частотой (скажем, 30% времени, если память служит), получается условная проверка быть немного дешевле.

Но это ORM от Django, и, вероятно, в оба конца базы данных или даже кэшированный результат, вероятно, будет доминировать над характеристиками производительности, поэтому удобство чтения в этом случае, поскольку вы ожидаете ровно один результат, используйте get().

27

Вы можете установить модуль под названием 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 
+0

, почему это раздражает такой метод? отлично смотрится! – Thomas

+2

@ Томас, я думаю, идея в том, что это раздражает НЕ иметь такой метод ... – user193130

0

Вариант 1 изящнее, но обязательно использовать try..except.

Из моего собственного опыта я могу сказать вам, что иногда вы уверены, что в базе данных не может быть более одного подходящего объекта, и все же будет два ... (за исключением, конечно, при получении объекта его основной ключ).

1

Предлагаю другой дизайн.

Если вы хотите, чтобы выполнить функцию возможного результата, вы могли бы извлечь из QuerySet, как это: http://djangosnippets.org/snippets/734/

Результат является довольно удивительным, вы могли бы, например:

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

Здесь фильтр возвращает пустой набор запросов или набор запросов с одним элементом. Ваши пользовательские функции набора запросов также можно связывать и повторно использовать. Если вы хотите выполнить его для всех своих записей: MyModel.objects.all().yourFunction().

Они также идеально подходит для использования в качестве действий в интерфейсе администратора:

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

Я играл с этой проблемой немного, и обнаружил, что вариант 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] 

При переключении на этот подход, я был в состоянии существенно сократить количество запросов моего приложение выполняется.

6

Я немного опоздал на вечеринку, но с Django 1.6 существует метод first() на запросах.

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


Возвращает первый объект согласованный по QuerySet, или None, если нет соответствия объекта. Если 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 

 Смежные вопросы

  • Нет связанных вопросов^_^