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


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

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

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

11 118

11 ответов:

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

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

вы можете установить модуль под названием Джанго-раздражает и потом сделать это:

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 является правильным. В Python исключение имеет равные накладные расходы на возврат. Для упрощенного доказательства вы можете посмотреть на этой.

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

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

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


возвращает первый объект, соответствующий набору запросов, или нет, если нет соответствующего объекта. Если в наборе запросов не определен порядок, то набор запросов автоматически упорядочивается первичным ключ.

пример:

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

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

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

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

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

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

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

from django.shortcuts import get_object_or_404

obj = get_object_or_404(MyModel, id=1)

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

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

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

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

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

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

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

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

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

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

1 является более элегантным, но обязательно использовать попробовать..кроме.