Вопрос

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

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

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

Это было полезно?

Решение

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 Это то, что Django делает в серверной части. get звонки filter и вызывает исключение, если элемент не найден или найдено более одного объекта.

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

https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models .запрос.Набор запросов.первый


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

Пример:

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().

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

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

from django.shortcuts import get_object_or_404

obj = get_object_or_404(MyModel, id=1)

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

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top