Filtr Django kontra get dla pojedynczego obiektu?

Prowadziłem debatę na ten temat z kilkoma kolegami. Czy istnieje preferowany sposób na odzyskanie obiektu w Django, gdy oczekujesz tylko jednego?

Dwa oczywiste sposoby to:

   try: 
      obj = MyModel.objects.get(id=1)
   except MyModel.DoesNotExist:
      # we have no object!  do something
      pass

I

   objs = MyModel.objects.filter(id=1)
   if len(objs) == 1:
      obj = objs[0]
   else: 
      # we have no object!  do something
      pass

Pierwsza metoda wydaje się bardziej poprawna, ale używa WYJĄTKÓW w przepływie sterowania, które mogą wprowadzić pewne koszty ogólne. Drugi jest bardziej rondo, ale nigdy nie będzie stanowić wyjątku.

Jakieś przemyślenia, które z nich jest lepsze? Czyli więcej wydajny?

Author: Peter Mortensen, 2009-06-19

11 answers

get() jest przewidziane specjalnie dla tego przypadku. Użyj go.

Opcja 2 jest prawie dokładnie taka, jak metoda get() jest faktycznie zaimplementowana w Django, więc nie powinno być żadnej różnicy "wydajności" (a fakt, że o niej myślisz, wskazuje na to, że łamiesz jedną z kardynalnych zasad programowania, a mianowicie próbujesz zoptymalizować kod zanim w ogóle zostanie napisany i wyprofilowany-dopóki nie będziesz miał kodu i nie będziesz mógł go uruchomić, nie wiesz jak on będzie działał, i próbujesz zoptymalizować kod. wcześniej jest ścieżka bólu).

 149
Author: James Bennett,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-19 20:04:19

Możesz zainstalować moduł o nazwie django-annoying a następnie zrobić to:

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
 29
Author: priestc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-20 02:19:06

1 jest poprawna. W Pythonie wyjątek ma taką samą wartość nadrzędną jak zwrot. Dla uproszczonego dowodu możesz spojrzeć na to .

2 To właśnie robi Django w backendzie. get wywołuje filter i podnosi wyjątek, jeśli nie znaleziono żadnego elementu lub jeśli znaleziono więcej niż jeden obiekt.

 14
Author: Mohammad Umair,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-28 14:07:13

Jestem trochę spóźniony na imprezę, ale w Django 1.6 jest metoda first() na querysetach.

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


Zwraca pierwszy obiekt dopasowany przez queryset, lub None, jeśli nie ma pasującego obiektu. Jeżeli Zestaw zapytań nie ma zdefiniowanej kolejności, wtedy zestaw zapytań jest automatycznie porządkowany przez klucz główny.

Przykład:

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
 8
Author: BastiBen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-14 17:32:56

Nie mogę mówić z żadnym doświadczeniem w Django, ale opcja # 1 wyraźnie mówi systemowi, że prosisz o 1 obiekt, podczas gdy druga opcja nie. Oznacza to, że opcja # 1 może łatwiej korzystać z indeksów pamięci podręcznej lub bazy danych, zwłaszcza gdy atrybut, na którym filtrujesz, nie jest gwarantowany jako unikalny.

Również (znowu spekulując) druga opcja może wymagać utworzenia pewnego rodzaju kolekcji wyników lub obiektu iteratora, ponieważ wywołanie filter() może normalnie zwraca wiele wierszy. Obejdziełbyś to za pomocą get().

Wreszcie, pierwsza opcja jest zarówno krótsza, jak i pomija dodatkową zmienną tymczasową - tylko niewielka różnica, ale co trochę pomaga.

 7
Author: Kylotan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-19 16:45:23

Trochę więcej informacji o wyjątkach. Jeśli nie zostaną podniesione, kosztują prawie nic. Tak więc, jeśli wiesz, że prawdopodobnie będziesz miał wynik, użyj wyjątku, ponieważ używając wyrażenia warunkowego ponosisz koszty sprawdzania za każdym razem, bez względu na wszystko. Z drugiej strony, kosztują nieco więcej niż wyrażenie warunkowe, gdy są podniesione, więc jeśli spodziewasz się, że wynik nie będzie miał określonej częstotliwości (powiedzmy 30% czasu, jeśli pamięć służy), sprawdzenie warunkowe okaże się być trochę taniej.

Ale to jest ORM Django i prawdopodobnie podróż w obie strony do bazy danych, lub nawet wynik w pamięci podręcznej, prawdopodobnie zdominuje charakterystykę wydajności, więc korzystaj z czytelności, w tym przypadku, ponieważ oczekujesz dokładnie jednego wyniku, użyj get().

 7
Author: SingleNegationElimination,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-07 09:43:36

Dlaczego to wszystko działa? Zamień 4 linie na 1 wbudowany Skrót. (To robi swój własny try / except.)

from django.shortcuts import get_object_or_404

obj = get_object_or_404(MyModel, id=1)
 6
Author: krubo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-20 01:25:03

Pobawiłem się trochę z tym problemem i odkryłem, że opcja 2 wykonuje dwa zapytania SQL, co dla tak prostego zadania jest przesadne. Zobacz moją adnotację:

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

Równoważną wersją wykonującą pojedyncze zapytanie jest:

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]

Przechodząc na to podejście, byłem w stanie znacznie zmniejszyć liczbę zapytań, które wykonuje moja aplikacja.

 3
Author: Jan Wrobel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-16 11:46:08

Ciekawe pytanie, ale dla mnie opcja # 2 śmierdzi przedwczesną optymalizacją. Nie jestem pewien, który jest bardziej wydajny, ale opcja #1 na pewno wygląda i czuje się bardziej pythonic dla mnie.

 1
Author: John McCollum,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-19 16:36:26

Proponuję inny projekt.

Jeśli chcesz wykonać funkcję na możliwym wyniku, możesz wyprowadzić z QuerySet, tak: http://djangosnippets.org/snippets/734/

Wynik jest całkiem niesamowity, można na przykład:

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

Tutaj filtr zwraca pusty zestaw zapytań lub zestaw zapytań z pojedynczym elementem. Twoje niestandardowe funkcje queryset są również łańcuszkowe i wielokrotnego użytku. Jeśli chcesz go wykonać dla wszystkich swoich wpisów: MyModel.objects.all().yourFunction().

Są również idealny do użycia jako akcje w interfejsie administratora:

def yourAction(self, request, queryset):
    queryset.yourFunction()
 1
Author: joctee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-15 07:50:27

Opcja 1 jest bardziej elegancka, ale pamiętaj, aby użyć try..z wyjątkiem.

Z własnego doświadczenia mogę ci powiedzieć, że czasami jesteś pewien, że nie może być więcej niż jeden pasujący obiekt w bazie danych, a jednak będą dwa... (oczywiście poza uzyskaniem obiektu przez jego klucz główny).

 0
Author: zooglash,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-22 11:40:02