Фильтр 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.
Из моего собственного опыта я могу сказать вам, что иногда вы уверены, что в базе данных не может быть больше одного подходящего объекта, и все же будет два... (за исключением, конечно, при получении объекта по его первичной ключ).