Фильтр Django против get для одного объекта?


147

Я спорил об этом с некоторыми коллегами. Есть ли предпочтительный способ получения объекта в 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

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

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

Ответы:


177

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

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


Все правильно, но, возможно, следует добавить дополнительную информацию, чтобы ответить? 1. Python поощряет попытку / исключение (см. EAFP ), поэтому QS.get()это хорошо. 2. Детали имеют значение: означает ли «ожидание только одного» всегда 0-1 объектов, или возможно иметь более 2 объектов, и этот случай также должен быть обработан (в этом случае len(objs)это ужасная идея)? 3. Не предполагайте что-либо о накладных расходах без эталона (я думаю, что в этом случае try/exceptбудет быстрее, если хотя бы половина вызовов что-то
ответит

> а именно, попытка оптимизировать код еще до того, как он был написан и профилирован Это интересное замечание. Я всегда думал, что мне следует подумать о наиболее факультативном способе реализации чего-либо до его реализации. Это неправильно? Можете ли вы уточнить этот момент? Есть ли какой-то ресурс, который объясняет это подробно?
Парф Шарма

Я удивлен, что никто не упомянул первым (). Другие советы, похоже, указывают на то, что это призыв к этому сценарию. stackoverflow.com/questions/5123839/…
NeilG

29

Вы можете установить модуль с именем 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
почему раздражает такой метод? выглядит хорошо для меня!
Томас

17

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

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


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

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

12

Я немного опоздал на вечеринку, но в 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

Это не гарантирует, что у вас есть только один объект в запросе
py_dude

8

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

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

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


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

8

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

from django.shortcuts import get_object_or_404

obj = get_object_or_404(MyModel, id=1)

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

2
То есть то , что Model.objects.get_or_create()для
boatcoder

7

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

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


4

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

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


1

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


1

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

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

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

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

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

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

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

0

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

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


0

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

Если пользователь запрашивает идентификатор, я могу запустить:

Record.objects.get(pk=id)

который выдает ошибку в django paginator, потому что это Record, а не Queryset of Records.

Мне нужно бежать:

Record.objects.filter(pk=id)

Который возвращает Queryset с одним элементом в нем. Тогда пагинатор работает просто отлично.


Чтобы использовать paginator - или любую функцию, которая ожидает QuerySet - ваш запрос должен возвращать QuerySet. Не переключайтесь между использованием .filter () и .get (), придерживайтесь .filter () и поставляйте фильтр «pk = id», как вы уже поняли. Это шаблон для этого варианта использования.
Корнел Массон

0

.получить()

Возвращает объект, соответствующий заданным параметрам поиска, который должен быть в формате, описанном в полевых поисках.

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

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

.фильтр()

Возвращает новый QuerySet, содержащий объекты, которые соответствуют заданным параметрам поиска.

Заметка

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.