Джанго-фільтр проти отримання для одного об'єкта?


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буде швидше до тих пір, по крайней мере , половина дзвінків повернути що - то)
imposeren

> а саме намагання оптимізувати код до того, як він навіть буде написаний та профільований Це цікаве зауваження. Я завжди думав, що я повинен продумати найбільш необов'язковий спосіб втілити щось, перш ніж це здійснити. Це неправильно? Чи можете ви детальніше зупинитися на цьому? Чи є якийсь ресурс, який детально пояснює це?
Parth Sharma

Я здивований, що ніхто не згадав перший (). Інші поради, схоже, вказують, що це дзвінок за цим сценарієм. 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 Це те, що робить Джанго в бекенде. 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

Я не можу спілкуватися з будь-яким досвідом Джанго, але варіант №1 чітко повідомляє системі, що ви просите 1 об’єкт, тоді як другий варіант - ні. Це означає, що варіант №1 може легше скористатися індексами кешу чи бази даних, особливо там, де атрибут, який ви фільтруєте, не гарантовано є унікальним.

Також (знову ж таки, розмірковуючи) другий варіант, можливо, повинен створити якусь колекцію результатів або об’єкт ітератора, оскільки виклик filter () може нормально повертати багато рядків. Ви обійшли це за допомогою get ().

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


Немає досвіду роботи з Джанго, але все ж на місці. Будучи явними, лаконічними та безпечними за замовчуванням, це хороші принципи, незалежно від мови та структури.
nevelis

8

Чому все це працює? Замініть 4 рядки на 1 вбудований ярлик. (Це робиться власною спробою / крім.)

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% часу, якщо пам'ять служить), виходить умовна перевірка бути трохи дешевше.

Але це ОРМ Джанго, і, ймовірно, зворотний шлях до бази даних або навіть кешований результат, ймовірно, домінуватиме у характеристиках продуктивності, тому сприяйте читанність у цьому випадку, оскільки ви очікуєте, що саме один результат використовувати 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 є більш елегантним, але обов'язково використовуйте спробуйте.

Зі свого власного досвіду можу сказати вам, що іноді ви впевнені, що в базі даних можливо не більше одного відповідного об’єкта, але все-таки буде два ... (за винятком, звичайно, при отриманні об'єкта за його первинним ключем).


0

Вибачте, я ще додав цю проблему, але я використовую програма-програматор django, і в моєму додатку для адміністрування даних користувач може вибрати те, про що слід запитувати. Іноді це ідентифікатор документа, але в іншому випадку це загальний запит, який повертає більше одного об'єкта, тобто набір запитів.

Якщо користувач запитує ідентифікатор, я можу запустити:

Record.objects.get(pk=id)

яка видає помилку в пагінаторі django, оскільки це запис, а не запит набору записів.

Мені потрібно бігти:

Record.objects.filter(pk=id)

Який повертає набір запитів з одним елементом у ньому. Тоді пагинатор працює чудово.


Щоб скористатись інструментом для записування сторінки (або будь-якою функціональністю, яка очікує QuerySet), ваш запит повинен повернути QuerySet. Не перемикайтеся між використанням .filter () та .get (), дотримуйтесь .filter () та постачайте фільтр "pk = id", як ви вже зрозуміли. Такий зразок для цього випадку використання.
Корнел Массон

0

.get ()

Повертає об'єкт, що відповідає заданим параметрам пошуку, який повинен бути у форматі, описаному в полі пошуку.

get () підвищує MultipleObjectsReturned, якщо було знайдено більше одного об'єкта. Виняток MultipleObjectsReturned є атрибутом класу моделі.

get () піднімає виняток DoesNotExist, якщо об'єкт не був знайдений для заданих параметрів. Цей виняток також є атрибутом модельного класу.

.filter ()

Повертає новий QuerySet, що містить об'єкти, які відповідають заданим параметрам пошуку.

Примітка

використовуйте get (), коли ви хочете отримати єдиний унікальний об'єкт, і filter (), коли ви хочете отримати всі об'єкти, які відповідають вашим параметрам пошуку.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.