Filtr Django a get dla pojedynczego obiektu?

147

Prowadziłem debatę na ten temat z kilkoma kolegami. Czy istnieje preferowany sposób pobierania obiektu w Django, gdy spodziewasz się 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 behawioralnie, ale używa wyjątków w przepływie sterowania, co może powodować pewne obciążenie. Drugi jest bardziej okrężny, ale nigdy nie spowoduje wyjątku.

Jakieś przemyślenia, które z nich są lepsze? Który jest bardziej wydajny?

Cory
źródło

Odpowiedzi:

177

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

Opcja 2 jest prawie dokładnie taka, jak ta get()metoda jest faktycznie zaimplementowana w Django, więc nie powinno być różnicy w wydajności (a fakt, że o niej myślisz, wskazuje, że naruszasz jedną z kardynalnych zasad programowania, a mianowicie próbę optymalizuj kod, zanim jeszcze zostanie napisany i sprofilowany - dopóki nie masz kodu i nie możesz go uruchomić, nie wiesz, jak będzie działać, a próba optymalizacji wcześniej jest ścieżką bólu).

James Bennett
źródło
Wszystko się zgadza, ale może do odpowiedzi należy dodać więcej informacji? 1. Python zachęca do prób / z wyjątkiem (patrz EAFP ), dlatego QS.get()jest dobry. 2. Szczegóły mają znaczenie: czy „oczekiwanie tylko jednego” oznacza zawsze 0-1 obiektów, czy też można mieć 2+ obiektów i ten przypadek też powinien być załatwiony (w tym przypadku len(objs)jest to okropny pomysł)? 3. Nie zakładaj niczego na temat narzutów bez benchmarku (myślę, że w tym przypadku try/exceptbędzie szybciej, o ile przynajmniej połowa połączeń coś zwróci)
imposeren
> a mianowicie próba optymalizacji kodu, zanim jeszcze zostanie napisany i sprofilowany. To jest interesująca uwaga. Zawsze myślałem, że powinienem pomyśleć o najbardziej opcjonalnym sposobie implementacji czegoś przed wdrożeniem. Czy to źle? Czy możesz rozwinąć tę kwestię? Czy jest jakiś zasób, który szczegółowo to wyjaśnia?
Parth Sharma
Jestem zaskoczony, że nikt o tym nie wspomniał jako pierwszy (). Inne rady zdają się wskazywać, że jest to wezwanie do tego scenariusza. stackoverflow.com/questions/5123839/…
NeilG
29

Możesz zainstalować moduł o nazwie django-denerwujący, 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
ksiądzc
źródło
1
dlaczego taka metoda jest irytująca? dla mnie wygląda dobrze!
Thomas
17

1 jest poprawne. W Pythonie wyjątek ma taki sam narzut w stosunku do zwrotu. Aby uzyskać uproszczony dowód, możesz spojrzeć na to .

2 To właśnie robi Django na zapleczu. getwywołuje filteri zgłasza wyjątek, jeśli nie zostanie znaleziony żaden element lub jeśli zostanie znalezionych więcej niż jeden obiekt.

Umair Mohammad
źródło
1
Ten test jest dość niesprawiedliwy. Dużą częścią narzutu związanego z wyrzucaniem wyjątku jest obsługa śladu stosu. Ten test miał długość stosu 1, czyli znacznie mniej niż zwykle w aplikacji.
Rob Young
@Rob Young: Co masz na myśli? Gdzie widzisz obsługę śladów stosu w typowym schemacie „poproś o wybaczenie zamiast o pozwolenie”? Czas przetwarzania zależy od odległości pokonywanej przez wyjątek, a nie od tego, jak głęboko to wszystko się dzieje (kiedy nie piszemy w Javie i nie wywołujemy e.printStackTrace ()). I najczęściej (jak przy przeszukiwaniu słownika) - wyjątek jest rzucany tuż pod try.
Tomasz Gandor
12

Trochę spóźniłem się na imprezę, ale w Django 1.6 jest first()metoda na querysets.

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


Zwraca pierwszy obiekt dopasowany przez zestaw zapytań lub None, jeśli nie ma pasującego obiektu. Jeśli QuerySet nie ma zdefiniowanej kolejności, to zestaw zapytań jest automatycznie porządkowany według klucza podstawowego.

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
BastiBen
źródło
Nie gwarantuje to, że masz tylko jeden obiekt w zapytaniu
py_dude
8

Nie mogę rozmawiać z żadnym doświadczeniem Django, ale opcja nr 1 jasno mówi systemowi, że prosisz o 1 obiekt, podczas gdy druga opcja nie. Oznacza to, że opcja nr 1 może łatwiej skorzystać z indeksów pamięci podręcznej lub baz danych, zwłaszcza gdy atrybut, według którego filtrujesz, nie jest gwarantowany jako unikalny.

Również (ponownie spekulując) druga opcja może wymagać utworzenia jakiegoś rodzaju kolekcji wyników lub obiektu iteratora, ponieważ wywołanie filter () mogłoby normalnie zwrócić wiele wierszy. Pominąłbyś to za pomocą get ().

Wreszcie pierwsza opcja jest krótsza i pomija dodatkową zmienną tymczasową - tylko niewielka różnica, ale każda drobna różnica pomaga.

Kylotan
źródło
Brak doświadczenia z Django, ale wciąż trafiony. Jasne, zwięzłe i bezpieczne domyślnie są dobrymi zasadami niezależnie od języka czy struktury.
nevelis,
8

Dlaczego to wszystko działa? Zamień 4 linie na 1 wbudowany skrót. (To robi swoje własne próby / z wyjątkiem.)

from django.shortcuts import get_object_or_404

obj = get_object_or_404(MyModel, id=1)
krubo
źródło
1
Jest to świetne, gdy jest to pożądane zachowanie, ale czasami możesz chcieć utworzyć brakujący obiekt lub wyciągnięcie było opcjonalne.
SingleNegationElimination
2
Po to Model.objects.get_or_create()jest
boatcoder
7

Więcej informacji o wyjątkach. Jeśli nie zostaną podniesione, prawie nic nie kosztują. Jeśli więc wiesz, że prawdopodobnie uzyskasz wynik, użyj wyjątku, ponieważ używając wyrażenia warunkowego płacisz koszty sprawdzenia 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 nie będą miały wyniku z pewną częstotliwością (powiedzmy, 30% czasu, jeśli pamięć służy), sprawdzenie warunkowe okaże się być trochę tańszym.

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

SingleNegationElimination
źródło
4

Bawiłem się trochę tym problemem i odkryłem, że opcja 2 wykonuje dwa zapytania SQL, co dla tak prostego zadania jest zbyteczne. 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żna wersja, która wykonuje pojedyncze zapytanie to:

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]

Przełączając się na to podejście, byłem w stanie znacznie zmniejszyć liczbę zapytań wykonywanych przez moją aplikację.

Jan Wróbel
źródło
1

Ciekawe pytanie, ale dla mnie opcja nr 2 cuchnie przedwczesną optymalizacją. Nie jestem pewien, który jest bardziej wydajny, ale opcja nr 1 z pewnością wygląda i wydaje mi się bardziej pytoniczna.

John McCollum
źródło
1

Proponuję inny projekt.

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

Wynik jest niesamowity, możesz na przykład:

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

W tym przypadku filtr zwraca pusty zestaw zapytań lub zestaw zapytań z pojedynczym elementem. Twoje niestandardowe funkcje zestawu zapytań również można łączyć w łańcuchy i używać ponownie. Jeśli chcesz wykonać to dla wszystkich wpisów: MyModel.objects.all().yourFunction().

Są również idealne do wykorzystania jako akcje w interfejsie administratora:

def yourAction(self, request, queryset):
    queryset.yourFunction()
joctee
źródło
0

Opcja 1 jest bardziej elegancka, ale pamiętaj, aby użyć try..except.

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 mimo to będą dwa ... (z wyjątkiem oczywiście gdy otrzymujesz obiekt za pomocą klucza podstawowego).

zooglash
źródło
0

Przepraszam, że dodam jeszcze jedno podejście do tego problemu, ale używam paginatora django, aw mojej aplikacji do administrowania danymi użytkownik może wybrać, czego dotyczy zapytania. Czasami jest to identyfikator dokumentu, ale poza tym jest to ogólne zapytanie zwracające więcej niż jeden obiekt, np. Queryset.

Jeśli użytkownik zapyta o identyfikator, mogę uruchomić:

Record.objects.get(pk=id)

co powoduje błąd w paginatorze django, ponieważ jest to Rekord, a nie Zestaw Zapytań Rekordów.

Muszę biec:

Record.objects.filter(pk=id)

Która zwraca Queryset z jednym elementem. Wtedy paginator działa dobrze.

excyberlabber
źródło
Aby użyć paginatora - lub dowolnej funkcji, która oczekuje QuerySet - zapytanie musi zwrócić QuerySet. Nie przełączaj się między używaniem .filter () i .get (), trzymaj się .filter () i podaj filtr "pk = id", jak już zdałeś sobie sprawę. To jest wzorzec dla tego przypadku użycia.
Cornel Masson
0

.dostać()

Zwraca obiekt pasujący do podanych parametrów wyszukiwania, który powinien być w formacie opisanym w sekcji Wyszukiwanie pól.

get () wywołuje MultipleObjectsReturned, jeśli znaleziono więcej niż jeden obiekt. Wyjątek MultipleObjectsReturned jest atrybutem klasy modelu.

get () zgłasza wyjątek DoesNotExist, jeśli nie znaleziono obiektu dla podanych parametrów. Ten wyjątek jest również atrybutem klasy modelu.

.filtr()

Zwraca nowy zestaw QuerySet zawierający obiekty, które pasują do podanych parametrów wyszukiwania.

Uwaga

użyj get (), jeśli chcesz uzyskać pojedynczy unikalny obiekt, i filter (), jeśli chcesz uzyskać wszystkie obiekty, które pasują do parametrów wyszukiwania.

Razia Khan
źródło