Question

Je discutais de cela avec des 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 les suivantes:

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?

Était-ce utile?

La solution

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

L'option 2 décrit presque exactement comment la méthode get () est réellement implémentée dans Django. Il ne devrait donc pas y avoir de "performance". différence (et le fait que vous y réfléchissiez indique que vous violez l’une des règles fondamentales de la 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 Je ne sais pas comment cela va fonctionner, et essayer d'optimiser avant est un chemin douloureux).

Autres conseils

Vous pouvez installer un module appelé django-annoying , puis procédez ainsi:

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 est correct. En Python, une exception entraîne une surcharge égale à un retour. Pour une preuve simplifiée, vous pouvez consulter ceci .

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

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 à la requête, ou Aucun 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

Je ne peux parler d'aucune expérience de Django, mais l'option n ° 1 indique clairement au système que vous demandez un seul objet, contrairement à la deuxième option. Cela signifie que l'option n ° 1 pourrait plus facilement tirer parti des index de cache ou de base de données, notamment lorsque l'attribut sur lequel vous filtrez n'est pas garanti d'être unique.

De même (à nouveau, en spéculant), la deuxième option peut devoir 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 aide utile.

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)

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 en utilisant 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 c'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 < code> 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 pour une tâche aussi simple est excessif. 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.

Question intéressante, mais pour moi l'option n ° 2 fleure l'optimisation prématurée. Je ne sais pas ce qui est le plus performant, mais l'option n ° 1 a certainement plus l'air et me semble plus pythonique.

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 pouvez par exemple:

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

Ici, le filtre renvoie soit un ensemble de requêtes vide, soit 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 le faire pour toutes vos entrées: MyModel.objects.all (). YourFunction () .

Elles sont également idéales pour être utilisées comme actions dans l'interface d'administration:

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

L'option 1 est plus élégante, mais assurez-vous d'utiliser try..except.

De ma propre expérience, je peux vous dire que parfois vous êtes sûr qu'il ne peut pas y avoir plus d'un objet correspondant dans la base de données, et pourtant il y en aura deux ... clé).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top