Django Filter versus Get für Einzelobjekt?

147

Ich hatte mit einigen Kollegen eine Debatte darüber. Gibt es eine bevorzugte Möglichkeit, ein Objekt in Django abzurufen, wenn Sie nur eines erwarten?

Die zwei offensichtlichen Wege sind:

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

Und:

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

if len(objs) == 1:
    obj = objs[0]
else:
    # We have no object! Do something...
    pass

Die erste Methode scheint verhaltensmäßig korrekter zu sein, verwendet jedoch Ausnahmen im Kontrollfluss, die einen gewissen Overhead verursachen können. Der zweite ist mehr Kreisverkehr, wird aber nie eine Ausnahme auslösen.

Irgendwelche Gedanken darüber, welche davon vorzuziehen sind? Welches ist effizienter?

Cory
quelle

Antworten:

177

get()ist speziell für diesen Fall vorgesehen . Benutze es.

Option 2 ist fast genau, wie die get()Methode tatsächlich in Django implementiert ist, daher sollte es keinen "Leistungsunterschied" geben (und die Tatsache, dass Sie darüber nachdenken, zeigt an, dass Sie gegen eine der Grundregeln der Programmierung verstoßen, nämlich zu versuchen Code optimieren, bevor er überhaupt geschrieben und profiliert wurde - bis Sie den Code haben und ihn ausführen können, wissen Sie nicht, wie er funktioniert, und der Versuch, ihn vorher zu optimieren, ist ein Schmerzenspfad).

James Bennett
quelle
Alles ist korrekt, aber vielleicht sollten weitere Informationen hinzugefügt werden, um zu antworten? 1. Python ermutigt zum Ausprobieren / Ausnehmen (siehe EAFP ), deshalb QS.get()ist es gut. 2. Details sind wichtig: Bedeutet "nur eines erwarten" immer 0-1 Objekte, oder es ist möglich, 2+ Objekte zu haben, und dieser Fall sollte auch behandelt werden (in diesem Fall len(objs)ist eine schreckliche Idee)? 3. Nehmen Sie nichts über Overhead ohne Benchmark an (ich denke, dass in diesem Fall try/exceptschneller sein wird, solange mindestens die Hälfte der Anrufe etwas
zurückgibt
> nämlich versuchen, Code zu optimieren, bevor er überhaupt geschrieben und profiliert wurde Dies ist eine interessante Bemerkung. Ich habe immer gedacht, dass ich mir überlegen sollte, wie ich etwas am optionalsten implementieren kann, bevor ich es implementiere. Ist das falsch? Können Sie diesen Punkt näher erläutern? Gibt es eine Ressource, die dies ausführlich erklärt?
Parth Sharma
Ich bin überrascht, dass niemand zuerst erwähnt hat (). Andere Ratschläge scheinen darauf hinzudeuten, dass dies der Anruf für dieses Szenario ist. stackoverflow.com/questions/5123839/…
NeilG
29

Sie können ein Modul namens django-nervig installieren und dann Folgendes tun:

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
Priester
quelle
1
Warum ist es ärgerlich, eine solche Methode zu haben? sieht gut aus für mich!
Thomas
17

1 ist richtig. In Python hat eine Ausnahme den gleichen Aufwand wie eine Rückgabe. Für einen vereinfachten Beweis können Sie dies betrachten .

2 Dies ist, was Django im Backend tut. getruft filtereine Ausnahme auf und löst sie aus, wenn kein Element gefunden wird oder wenn mehr als ein Objekt gefunden wird.

Umair Mohammad
quelle
1
Dieser Test ist ziemlich unfair. Ein großer Teil des Overheads beim Auslösen einer Ausnahme ist die Behandlung der Stapelverfolgung. Dieser Test hatte eine Stapellänge von 1, was viel weniger ist, als Sie normalerweise in einer Anwendung finden würden.
Rob Young
@ Rob Young: Was meinst du? Wo sehen Sie die Stapelverfolgungsbehandlung im typischen Schema "Bitte um Vergebung statt um Erlaubnis"? Die Verarbeitungszeit hängt von der Entfernung ab, die die Ausnahme zurücklegt, und nicht davon, wie tief alles passiert (wenn wir nicht in Java schreiben und e.printStackTrace () aufrufen). Und meistens (wie bei der Wörterbuchsuche) wird die Ausnahme direkt unter dem ausgelöst try.
Tomasz Gandor
12

Ich bin etwas spät zur Party, aber mit Django 1.6 gibt es die first()Methode für Abfragesätze.

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


Gibt das erste Objekt zurück, das mit dem Abfragesatz übereinstimmt, oder None, wenn kein übereinstimmendes Objekt vorhanden ist. Wenn für das QuerySet keine Reihenfolge definiert ist, wird das Abfrageset automatisch nach dem Primärschlüssel sortiert.

Beispiel:

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
quelle
Es garantiert nicht, dass Sie nur ein Objekt in einer Abfrage haben
py_dude
8

Ich kann mit keiner Erfahrung von Django sprechen, aber Option 1 sagt dem System deutlich, dass Sie nach 1 Objekt fragen, während die zweite Option dies nicht tut. Dies bedeutet, dass Option 1 die Vorteile von Cache- oder Datenbankindizes leichter nutzen kann, insbesondere wenn nicht garantiert ist, dass das Attribut, nach dem Sie filtern, eindeutig ist.

Außerdem muss (erneut spekulierend) die zweite Option möglicherweise eine Art Ergebnissammlung oder ein Iteratorobjekt erstellen, da der Aufruf von filter () normalerweise viele Zeilen zurückgeben kann. Sie würden dies mit get () umgehen.

Schließlich ist die erste Option kürzer und lässt die zusätzliche temporäre Variable weg - nur ein kleiner Unterschied, aber jedes bisschen hilft.

Kylotan
quelle
Keine Erfahrung mit Django, aber immer noch genau richtig. Explizit, knapp und standardmäßig sicher zu sein, sind gute Grundsätze, unabhängig von Sprache oder Rahmen.
Nevelis
8

Warum funktioniert das alles? Ersetzen Sie 4 Zeilen durch 1 integrierte Verknüpfung. (Dies macht seinen eigenen Versuch / außer.)

from django.shortcuts import get_object_or_404

obj = get_object_or_404(MyModel, id=1)
krubo
quelle
1
Dies ist großartig, wenn es sich um das gewünschte Verhalten handelt. Manchmal möchten Sie jedoch möglicherweise das fehlende Objekt erstellen, oder der Pull war eine optionale Information.
SingleNegationElimination
2
Das ist, was Model.objects.get_or_create()für
Bootscodierer
7

Weitere Informationen zu Ausnahmen. Wenn sie nicht aufgezogen werden, kosten sie fast nichts. Wenn Sie also wissen, dass Sie wahrscheinlich ein Ergebnis erzielen werden, verwenden Sie die Ausnahme, da Sie bei Verwendung eines bedingten Ausdrucks die Kosten für die Überprüfung jedes Mal bezahlen, egal was passiert. Auf der anderen Seite kosten sie etwas mehr als einen bedingten Ausdruck, wenn sie ausgelöst werden. Wenn Sie also erwarten, dass Sie nicht mit einer bestimmten Häufigkeit ein Ergebnis erzielen (z. B. 30% der Zeit, wenn Speicherplatz zur Verfügung steht), wird die bedingte Prüfung durchgeführt ein bisschen billiger sein.

Dies ist jedoch Djangos ORM, und wahrscheinlich dominiert der Roundtrip zur Datenbank oder sogar ein zwischengespeichertes Ergebnis die Leistungsmerkmale. Begünstigen Sie daher in diesem Fall die Lesbarkeit, da Sie genau ein Ergebnis erwarten, verwenden Sie get().

SingleNegationElimination
quelle
4

Ich habe ein bisschen mit diesem Problem gespielt und festgestellt, dass die Option 2 zwei SQL-Abfragen ausführt, was für eine so einfache Aufgabe übermäßig ist. Siehe meine Anmerkung:

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

Eine äquivalente Version, die eine einzelne Abfrage ausführt, ist:

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]

Durch die Umstellung auf diesen Ansatz konnte ich die Anzahl der von meiner Anwendung ausgeführten Abfragen erheblich reduzieren.

Jan Wrobel
quelle
1

Interessante Frage, aber für mich stinkt Option 2 nach vorzeitiger Optimierung. Ich bin mir nicht sicher, was performanter ist, aber Option 1 sieht für mich auf jeden Fall pythonischer aus und fühlt sich auch so an.

John McCollum
quelle
1

Ich schlage ein anderes Design vor.

Wenn Sie eine Funktion für ein mögliches Ergebnis ausführen möchten, können Sie QuerySet wie folgt ableiten: http://djangosnippets.org/snippets/734/

Das Ergebnis ist ziemlich beeindruckend, Sie könnten zum Beispiel:

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

Hier gibt der Filter entweder ein leeres Abfrageset oder ein Abfrageset mit einem einzelnen Element zurück. Ihre benutzerdefinierten Abfragesatzfunktionen sind auch verkettbar und wiederverwendbar. Wenn Sie es für alle Ihre Einträge ausführen möchten:MyModel.objects.all().yourFunction() .

Sie eignen sich auch ideal als Aktionen in der Administrationsoberfläche:

def yourAction(self, request, queryset):
    queryset.yourFunction()
Joctee
quelle
0

Option 1 ist eleganter, aber verwenden Sie unbedingt try..except.

Aus eigener Erfahrung kann ich Ihnen sagen, dass Sie manchmal sicher sind, dass möglicherweise nicht mehr als ein übereinstimmendes Objekt in der Datenbank vorhanden sein kann, und dennoch zwei ... (außer natürlich, wenn Sie das Objekt anhand seines Primärschlüssels abrufen).

Zooglash
quelle
0

Es tut mir leid, dass ich noch eine Einstellung zu diesem Problem hinzugefügt habe, aber ich verwende den Django-Paginator. In meiner Datenadministrations-App kann der Benutzer auswählen, was abgefragt werden soll. Manchmal ist dies die ID eines Dokuments, ansonsten handelt es sich um eine allgemeine Abfrage, die mehr als ein Objekt zurückgibt, dh ein Abfragesatz.

Wenn der Benutzer die ID abfragt, kann ich Folgendes ausführen:

Record.objects.get(pk=id)

Dies wirft einen Fehler in Djangos Paginator auf, da es sich um einen Datensatz und nicht um eine Abfragemenge von Datensätzen handelt.

Ich muss rennen:

Record.objects.filter(pk=id)

Was ein Queryset mit einem Element zurückgibt. Dann funktioniert der Paginator einwandfrei.

Excyberlabber
quelle
Um den Paginator oder eine Funktion zu verwenden, die ein QuerySet erwartet, muss Ihre Abfrage ein QuerySet zurückgeben. Wechseln Sie nicht zwischen .filter () und .get (), bleiben Sie bei .filter () und geben Sie den Filter "pk = id" an, wie Sie bereits erkannt haben. Das ist das Muster für diesen Anwendungsfall.
Cornel Masson
0

.bekommen()

Gibt das Objekt zurück, das den angegebenen Suchparametern entspricht und das unter Feldsuche beschriebene Format haben sollte.

get () löst MultipleObjectsReturned aus, wenn mehr als ein Objekt gefunden wurde. Die MultipleObjectsReturned-Ausnahme ist ein Attribut der Modellklasse.

get () löst eine DoesNotExist-Ausnahme aus, wenn für die angegebenen Parameter kein Objekt gefunden wurde. Diese Ausnahme ist auch ein Attribut der Modellklasse.

.Filter()

Gibt ein neues QuerySet zurück, das Objekte enthält, die den angegebenen Suchparametern entsprechen.

Hinweis

Verwenden Sie get (), wenn Sie ein einzelnes eindeutiges Objekt abrufen möchten, und filter (), wenn Sie alle Objekte abrufen möchten, die Ihren Suchparametern entsprechen.

Razia Khan
quelle