web-dev-qa-db-fra.com

Django Filtre versus get pour un seul objet?

Je discutais de cela avec certains collègues. Existe-t-il un moyen privilégié de récupérer un objet dans Django lorsque vous en attendez un seul?

Les deux manières é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 avoir un comportement plus correct, mais utilise des exceptions dans le flux de contrôle, ce qui peut entraîner une surcharge. Le second est plus rond mais ne lèvera jamais d’exception.

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

131
Cory

get() est fourni spécifiquement pour ce cas. Utilise le.

L’option 2 décrit presque exactement la méthode réellement utilisée par Django dans la méthode get(). Il ne devrait donc y avoir aucune différence en termes de "performances" (et le fait que vous y réfléchissiez indique que vous violez l’un des principes les plus importants. règles de programmation, à savoir essayer d'optimiser le code avant même de l'écrire et de le profiler - tant que vous n'avez pas le code et que vous ne pouvez l'exécuter, vous ne savez pas comment il va fonctionner, et essayer d'optimiser avant est un chemin pénible) .

162
James Bennett

Vous pouvez installer un module appelé Django-ennoying puis procédez comme suit:

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
priestc

1 est correct. Dans Python, une exception a le même coût qu'un retour. Pour une preuve simplifiée, vous pouvez regarder this .

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

15
Sean Creeley

Je suis un peu en retard pour la fête, mais avec Django 1.6), il existe la méthode first() sur les ensembles de requêtes.

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


Retourne le premier objet correspondant au queryset, ou None s'il n'y a pas d'objet correspondant. Si aucun ordre n'est défini dans le jeu de requêtes, le jeu de requêtes est automatiquement commandé 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
9
BastiBen

Pourquoi tout ça marche? 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)
8
krubo

Je ne peux parler avec aucune expérience de Django mais l'option n ° 1 indique clairement au système que vous demandez un objet, alors que la deuxième option ne le fait pas. Cela signifie que l'option n ° 1 pourrait davantage Exploitez facilement les index de cache ou de base de données, en particulier lorsque l'attribut sur lequel vous filtrez n'est pas garanti d'être unique.

De plus (encore une fois, en spéculant), la deuxième option devra peut-être créer une sorte de collection de résultats ou un objet itérateur, car l'appel de filter () pourrait normalement renvoyer plusieurs lignes. Vous éviteriez cela avec get ().

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

8
Kylotan

Quelques informations supplémentaires sur les exceptions. S'ils ne sont pas élevés, ils ne coûtent presque rien. Ainsi, si vous savez que vous allez probablement avoir un résultat, utilisez l'exception, car si vous utilisez une expression conditionnelle, vous payez le coût de la vérification à chaque fois, quoi qu'il arrive. En revanche, elles coûtent un peu plus cher qu'une expression conditionnelle lorsqu'elles sont levées. Par conséquent, si vous vous attendez à ne pas obtenir un résultat avec une certaine fréquence (par exemple, 30% du temps, si la mémoire est bonne), la vérification conditionnelle s'avère être un peu moins cher.

Mais ceci est l'ORM de Django, et probablement l'aller-retour vers la base de données, voire un résultat mis en cache, est susceptible de dominer les caractéristiques de performance. Par conséquent, privilégiez la lisibilité. Dans ce cas, puisque vous attendez exactement un résultat, utilisez get().

J'ai un peu joué avec ce problème et découvert que l'option 2 exécute deux requêtes SQL, ce qui est excessif pour une tâche aussi simple. 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 requête unique 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 optant pour cette approche, j'ai pu réduire considérablement le nombre de requêtes exécutées par mon application.

4
Jan Wrobel

Question intéressante, mais pour moi l'option 2 pue l'optimisation prématurée. Je ne sais pas ce qui est le plus performant, mais l'option 1 semble certainement plus pythonique pour moi.

1
John McCollum

Je suggère un design différent.

Si vous souhaitez exécuter une fonction sur un résultat possible, vous pouvez dériver de QuerySet, comme suit: http://djangosnippets.org/snippets/734/

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

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

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

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

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

L'option 1 est plus élégante, 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 lorsque vous obtiendrez l'objet par sa clé primaire).

0
zooglash