2009-06-19 5 views
108

J'avais un débat là-dessus avec des collègues. Existe-t-il un moyen privilégié de récupérer un objet dans Django lorsque vous n'en attendez qu'un seul?Filtre Django versus obtenir un seul objet?

Les deux façons évidentes sont:

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

et

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

La première méthode semble plus correct behaviorally, mais utilise des exceptions dans le flux de contrôle qui peut introduire des frais généraux. Le second est plus rond mais ne déclenchera jamais une exception.

Avez-vous des idées sur lesquelles de ces méthodes est préférable? Lequel est le plus efficace?

Répondre

137

get() est prévu spécialement pour ce cas. Utilise le.

L'option 2 est presque exactement comment la méthode get() est réellement implémentée dans Django, donc il ne devrait y avoir aucune différence de "performance" (et le fait que vous y pensiez indique que vous violez l'une des règles cardinales de programmation, à savoir essayer d'optimiser le code avant même qu'il ne soit écrit et profilé - jusqu'à ce que vous ayez le code et que vous puissiez l'exécuter, vous ne savez pas comment il va fonctionner, et essayer d'optimiser avant est un chemin de douleur).

1

question intéressante, mais pour moi l'option # 2 relents d'optimisation prématurée. Je ne suis pas sûr de ce qui est le plus performant, mais l'option # 1 semble certainement plus pythonique à mes yeux.

6

Je ne peux pas parler avec une expérience de Django, mais l'option 1 indique clairement le système que vous demandez 1 objet, alors que la seconde option ne fonctionne pas. Cela signifie que l'option n ° 1 pourrait plus facilement tirer parti des index de cache ou de base de données, en particulier lorsque l'attribut sur lequel vous filtrez n'est pas garanti unique.

aussi (encore une fois, spéculant) la deuxième option peut avoir à créer une sorte de résultats collection ou objet iterator depuis le filtre() appel pourrait revenir normalement plusieurs lignes. Vous évitez ceci avec get().

Enfin, la première option est à la fois plus courte et omet la variable temporaire supplémentaire - seulement une différence mineure, mais chaque petite aide.

+0

Aucune expérience avec Django, mais toujours repérer. Être explicite, laconique et sûr par défaut, sont de bons principes, peu importe le langage ou le cadre. – nevelis

13

1 est correct. En Python, une exception a un surcoût équivalent à un retour. Pour une preuve simplifiée, vous pouvez regarder this.

2 C'est ce que fait Django dans le backend. get appelle filter et déclenche une exception si aucun élément n'est trouvé ou si plus d'un objet est trouvé.

+1

Ce test est assez injuste. Une grande partie de l'overhead dans le lancement d'une exception est la gestion de la trace de la pile. Ce test avait une longueur de pile de 1, ce qui est beaucoup plus bas que ce que l'on trouve habituellement dans une application. –

+0

@Rob Young: Que voulez-vous dire? Où voyez-vous la gestion des traces de pile dans le schéma typique «demander pardon plutôt que par permission»? Le temps de traitement dépend de la distance parcourue par l'exception, pas de la profondeur de tout cela (lorsque nous n'écrivons pas en Java et n'appelons pas e.printStackTrace()). Et le plus souvent (comme dans la recherche de dictionnaire) - l'exception est lancée juste en dessous du 'try'. –

5

Pourquoi tout cela fonctionne-t-il? Remplacez 4 lignes par 1 raccourci intégré. (Cela fait son propre essai/sauf.)

from django.shortcuts import get_object_or_404 

obj = get_object_or_404(MyModel, id=1) 
+1

C'est génial quand c'est le comportement désiré, mais parfois, vous pourriez vouloir créer l'objet manquant, ou le pull était l'information facultative. – SingleNegationElimination

+1

C'est ce que 'Model.objects.get_or_create()' est pour – boatcoder

6

Plus d'informations sur les exceptions. S'ils ne sont pas élevés, ils ne coûtent presque rien. Donc, si vous savez que vous allez probablement avoir un résultat, utilisez l'exception, car en utilisant une expression conditionnelle, vous payez le coût de la vérification à chaque fois, quoi qu'il arrive. D'autre part, ils coûtent un peu plus qu'une expression conditionnelle quand ils sont augmentés, donc si vous prévoyez ne pas avoir un résultat avec une certaine fréquence (disons, 30% du temps, si la mémoire sert), la vérification conditionnelle s'avère être un peu moins cher.

Mais c'est l'ORM de Django, et probablement l'aller-retour à la base de données, ou même un résultat caché, est susceptible de dominer les performances, donc privilégiez la lisibilité, car vous attendez exactement un résultat, utilisez get().

27

Vous pouvez installer un module appelé django-annoying et faites ceci:

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 
+0

pourquoi est-il embêtant d'avoir une telle méthode? ça me va bien! – Thomas

+2

@Thomas Je pense que l'idée est que c'est ennuyeux de ne pas avoir une telle méthode ... – user193130

0

Option 1 est plus élégant, mais assurez-vous d'utiliser try..except. D'après ma propre expérience, je peux vous dire que parfois vous êtes sûr qu'il ne peut y avoir plus d'un objet correspondant dans la base de données, et pourtant il y en aura deux ... (sauf bien sûr lors de l'obtention de l'objet par son clé primaire).

1

Je suggère un design différent.

Si vous souhaitez effectuer une fonction sur un résultat possible, vous pourrait tirer de QuerySet, comme ceci: http://djangosnippets.org/snippets/734/

Le résultat est assez impressionnant, vous pouvez par exemple:

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

Ici, Le filtre renvoie un jeu de requête vide ou un jeu de requête avec un seul élément. Vos fonctions de requête personnalisée sont également chaînables et réutilisables. Si vous voulez l'effectuer pour toutes vos entrées: MyModel.objects.all().yourFunction().

Ils sont également idéal pour être utilisé comme actions dans l'interface d'administration:

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

J'ai joué à ce problème un peu et découvert que l'option 2 exécute deux requêtes SQL, qui, pour un si simple la tâche est excessive. Voir mon annotation:

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 

Une version équivalente qui exécute une seule requête est:

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] 

En passant à cette approche, je suis en mesure de réduire considérablement nombre de requêtes ma demande est exécutée.

6

Je suis un peu en retard à la fête, mais avec Django 1.6 il y a la méthode first() sur les jeux de requête.

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


Renvoie le premier objet abondés par l'queryset, ou None s'il n'y a pas d'objet correspondant. Si le QuerySet n'a aucun ordre défini, le Queryset est automatiquement trié par la clé primaire.

Exemple:

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 
Questions connexes