Filtro Django contro get per singolo oggetto?


147

Stavo discutendo di questo con alcuni colleghi. C'è un modo preferito per recuperare un oggetto in Django quando te ne aspetti solo uno?

I due modi ovvi sono:

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

E:

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

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

Il primo metodo sembra comportamentalmente più corretto, ma utilizza eccezioni nel flusso di controllo che possono introdurre un sovraccarico. La seconda è più rotonda ma non solleverà mai un'eccezione.

Qualche idea su quale di questi è preferibile? Qual è più efficiente?

Risposte:


177

get()viene fornito appositamente per questo caso . Usalo

L'opzione 2 è quasi esattamente come il get()metodo è effettivamente implementato in Django, quindi non ci dovrebbero essere differenze di "prestazioni" (e il fatto che ci stai pensando indica che stai violando una delle regole cardine della programmazione, vale a dire provare a ottimizzare il codice prima ancora che sia stato scritto e profilato - fino a quando non si dispone del codice e non è possibile eseguirlo, non si sa come funzionerà, e provare a ottimizzare prima di allora è un percorso doloroso).


Tutto è corretto ma forse è necessario aggiungere ulteriori informazioni per rispondere? 1. Python incoraggia provare / tranne (vedi EAFP ), ecco perché QS.get()è buono. 2. I dettagli contano: "aspettarsi solo uno" significa sempre 0-1 oggetti, oppure è possibile avere 2+ oggetti e anche questo caso dovrebbe essere gestito (in questo caso len(objs)è un'idea terribile)? 3. Non dare per scontato un overhead senza un benchmark (penso che in questo caso try/exceptsarà più veloce fintanto che almeno la metà delle chiamate restituirà qualcosa)
imposeren

> vale a dire cercare di ottimizzare il codice prima ancora che sia stato scritto e profilato Questa è un'osservazione interessante. Ho sempre pensato che avrei dovuto pensare al modo più opzionale di implementare qualcosa prima di implementarlo. È sbagliato? Puoi approfondire questo punto? C'è qualche risorsa che spiega questo in dettaglio?
Parth Sharma,

Sono sorpreso che nessuno abbia menzionato prima (). Altri consigli sembrano indicare che è la chiamata effettuata per questo scenario. stackoverflow.com/questions/5123839/...
NeilG

29

Puoi installare un modulo chiamato django-fastidioso e quindi fare questo:

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
perché è fastidioso avere un tale metodo? mi sta bene!
Thomas,

17

1 è corretto. In Python un'eccezione ha lo stesso sovraccarico di un ritorno. Per una prova semplificata puoi guardare questo .

2 Questo è ciò che Django sta facendo nel backend. getchiama filtere genera un'eccezione se non viene trovato alcun elemento o se viene trovato più di un oggetto.


1
Quel test è abbastanza ingiusto. Gran parte dell'overhead nel generare un'eccezione è la gestione della traccia dello stack. Quel test aveva una lunghezza dello stack di 1 che è molto più bassa di quella che potresti trovare in un'applicazione.
Rob Young,

@Rob Young: cosa intendi? Dove vedi la gestione delle tracce dello stack nel tipico schema "chiedi perdono anziché permesso"? Il tempo di elaborazione dipende dalla distanza percorsa dall'eccezione, non dalla profondità con cui accade tutto (quando non stiamo scrivendo in Java e chiamando e.printStackTrace ()). E molto spesso (come nella ricerca nel dizionario) - l'eccezione viene generata appena sotto il try.
Tomasz Gandor,

12

Sono un po 'in ritardo alla festa, ma con Django 1.6 c'è il first()metodo sui queryset.

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


Restituisce il primo oggetto corrispondente al set di query o Nessuno se non è presente alcun oggetto corrispondente. Se QuerySet non ha alcun ordinamento definito, il queryset viene automaticamente ordinato dalla chiave primaria.

Esempio:

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

Non garantisce la presenza di un solo oggetto in una query
py_dude,

8

Non posso parlare con nessuna esperienza di Django ma l'opzione n. 1 dice chiaramente al sistema che stai chiedendo 1 oggetto, mentre la seconda opzione no. Ciò significa che l'opzione n. 1 potrebbe sfruttare più facilmente gli indici della cache o del database, in particolare laddove l'attributo su cui si sta filtrando non è garantito come unico.

Inoltre (di nuovo, ipotizzando) la seconda opzione potrebbe dover creare una sorta di raccolta di risultati o oggetto iteratore poiché la chiamata filter () potrebbe normalmente restituire molte righe. Lo aggireresti con get ().

Infine, la prima opzione è sia più breve che omette la variabile temporanea aggiuntiva - solo una differenza minore ma ogni piccolo aiuto.


Nessuna esperienza con Django ma ancora perfetto. Essere espliciti, concisi e sicuri per impostazione predefinita, sono buoni principi indipendentemente dalla lingua o dalla struttura.
nevelis,

8

Perché tutto questo funziona? Sostituisci 4 righe con 1 scorciatoia integrata. (Questo fa il suo tentativo / tranne.)

from django.shortcuts import get_object_or_404

obj = get_object_or_404(MyModel, id=1)

1
Questo è fantastico quando si tratta del comportamento desiderato, ma a volte potresti voler creare l'oggetto mancante o il pull era un'informazione facoltativa.
SingleNegationElimination,

2
Questo è ciò che Model.objects.get_or_create()serve
boatcoder

7

Qualche informazione in più sulle eccezioni. Se non vengono allevati, non costano quasi nulla. Quindi, se sai che probabilmente otterrai un risultato, usa l'eccezione, poiché usando un'espressione condizionale pagherai il costo del controllo ogni volta, qualunque cosa accada. D'altra parte, costano un po 'più di un'espressione condizionale quando vengono sollevati, quindi se ti aspetti di non avere un risultato con una certa frequenza (diciamo, il 30% delle volte, se la memoria serve), il controllo condizionale risulta essere un po 'più economico.

Ma questo è l'ORM di Django, e probabilmente il viaggio di andata e ritorno nel database, o anche un risultato memorizzato nella cache, probabilmente dominerà le caratteristiche delle prestazioni, quindi favorisci la leggibilità, in questo caso, poiché ti aspetti esattamente un risultato, usa get().


4

Ho giocato un po 'con questo problema e ho scoperto che l'opzione 2 esegue due query SQL, che per un'attività così semplice è eccessiva. Vedi la mia annotazione:

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

Una versione equivalente che esegue una singola query è:

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]

Passando a questo approccio, sono stato in grado di ridurre sostanzialmente il numero di query eseguite dalla mia applicazione.


1

Domanda interessante, ma per me l'opzione n. 2 puzza di ottimizzazione prematura. Non sono sicuro di quale sia il più performante, ma l'opzione n. 1 sembra sicuramente più ritmata per me.


1

Suggerisco un design diverso.

Se si desidera eseguire una funzione su un possibile risultato, è possibile derivare da QuerySet, in questo modo: http://djangosnippets.org/snippets/734/

Il risultato è davvero fantastico, potresti ad esempio:

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

Qui, il filtro restituisce un queryset vuoto o un queryset con un singolo elemento. Le tue funzioni di query personalizzate sono anche concatenabili e riutilizzabili. Se si desidera eseguire per tutte le voci: MyModel.objects.all().yourFunction().

Sono inoltre ideali per essere utilizzati come azioni nell'interfaccia di amministrazione:

def yourAction(self, request, queryset):
    queryset.yourFunction()

0

L'opzione 1 è più elegante, ma assicurati di usare try..except.

Dalla mia esperienza posso dirti che a volte sei sicuro che non ci possa essere più di un oggetto corrispondente nel database, eppure ci saranno due ... (tranne ovviamente quando si ottiene l'oggetto con la sua chiave primaria).


0

Mi dispiace aggiungere un'altra versione di questo problema, ma sto usando il paginator di django e nella mia app di amministrazione dei dati, l'utente è autorizzato a scegliere su cosa interrogare. A volte questo è l'id di un documento, ma per il resto è una query generale che restituisce più di un oggetto, ovvero un Queryset.

Se l'utente richiede l'id, posso eseguire:

Record.objects.get(pk=id)

che genera un errore nel paginator di Django, perché è un Record e non un Queryset of Records.

Ho bisogno di correre:

Record.objects.filter(pk=id)

Che restituisce un Queryset con un elemento al suo interno. Quindi il paginatore funziona bene.


Per utilizzare il paginatore o qualsiasi funzionalità che prevede un QuerySet, la query deve restituire un QuerySet. Non passare dall'uso di .filter () a .get (), attenersi a .filter () e fornire il filtro "pk = id", come già realizzato. Questo è lo schema per questo caso d'uso.
Cornel Masson,

0

.ottenere()

Restituisce l'oggetto che corrisponde ai parametri di ricerca indicati, che dovrebbe essere nel formato descritto in Ricerche sul campo.

get () genera MultipleObjectsReturned se viene trovato più di un oggetto. L'eccezione MultipleObjectsReturned è un attributo della classe del modello.

get () genera un'eccezione DoesNotExist se non è stato trovato un oggetto per i parametri indicati. Questa eccezione è anche un attributo della classe del modello.

.filtro()

Restituisce un nuovo QuerySet contenente oggetti che corrispondono ai parametri di ricerca specificati.

Nota

usa get () quando vuoi ottenere un singolo oggetto univoco, e filter () quando vuoi ottenere tutti gli oggetti che corrispondono ai tuoi parametri di ricerca.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.