Фильтр 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

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

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

Ответ 1

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

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

Ответ 2

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

Ответ 3

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

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

Ответ 4

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

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


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

Ответ 5

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

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

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

Ответ 6

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

from django.shortcuts import get_object_or_404

obj = get_object_or_404(MyModel, id=1)

Ответ 7

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

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

Ответ 8

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

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

Ответ 9

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

Ответ 10

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

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

Результат довольно увлекательный, вы можете, например:

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

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

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

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

Ответ 11

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

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