Фильтр 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 ответов:
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()