web-dev-qa-db-fra.com

Django comparaison des instances de modèle pour l'égalité

Je comprends qu'avec une situation singleton, vous pouvez effectuer une opération telle que:

spam == eggs

et si spam et eggs sont des instances de la même classe avec toutes les mêmes valeurs d'attribut, elle renverra True. Dans un modèle Django, cela est naturel car deux instances distinctes d'un modèle ne seront jamais identiques à moins d'avoir le même .pk valeur.

Le problème avec cela est que si une référence à une instance a des attributs qui ont été mis à jour par le middleware quelque part en cours de route et qu'elle n'a pas été enregistrée, et que vous essayez de le faire vers une autre variable contenant une référence à une instance de la même modèle, il retournera False bien sûr car ils ont des valeurs différentes pour certains des attributs. Évidemment, je n'ai pas besoin de quelque chose comme un singleton , mais je me demande s'il y a une méthode officielle Djangonic (ha, un nouveau mot) pour vérifier cela, ou si je devrais simplement vérifier que le .pk la valeur est la même, en exécutant:

spam.pk == eggs.pk

Je suis désolé si cela a été une énorme perte de temps, mais il semble qu'il puisse y avoir une méthode pour le faire et quelque chose qui me manque que je regretterai plus tard si je ne le trouve pas.

MISE À JOUR (27/02/2015)

Vous ne devez pas tenir compte de la première partie de cette question, car vous ne devez pas comparer les singletons avec ==, mais plutôt avec is. Les singletons n'ont vraiment rien à voir avec cette question.

41
orokusaki

De documentation Django :

Pour comparer deux instances de modèle, utilisez simplement l’opérateur de comparaison standard Python, le signe double égal: ==. Dans les coulisses, qui compare les valeurs de clé primaire de deux modèles.

76
Gunnar

spam.pk == eggs.pk est un bon moyen de le faire.

Vous pouvez ajouter __eq__ à votre modèle, mais je vais éviter cela, car cela porte à confusion en tant que == peut signifier différentes choses dans différents contextes, par exemple Je veux peut-être == pour signifier que le contenu est le même, l'ID peut différer.

spam.pk == eggs.pk

Edit: btw in Django 1.0.2 La classe de modèle a défini __eq__ as

def __eq__(self, other):
    return isinstance(other, self.__class__) and self._get_pk_val() == other._get_pk_val() 

qui semble être identique à spam.pk == eggs.pk car pk est une propriété qui utilise _get_pk_val donc je ne vois pas pourquoi spam == eggs ne fonctionne pas ?

18
Anurag Uniyal

À partir de Django 3.0.2, le code source pour l'égalité d'instance de modèle est this :

def __eq__(self, other):
    if not isinstance(other, Model):
        return False
    if self._meta.concrete_model != other._meta.concrete_model:
        return False
    my_pk = self.pk
    if my_pk is None:
        return self is other
    return my_pk == other.pk

Autrement dit, deux instances de modèle sont égales si elles proviennent de la même table de base de données et ont la même clé primaire. Si l'une des clés primaires est None, elles ne sont égales que si elles sont le même objet.

(Donc, pour revenir à la question du PO, il suffit de comparer les instances.)

11

Vous pouvez définir la classe '__eq__ méthode pour modifier ce comportement:

http://docs.python.org/reference/datamodel.html

6
piotr

Juste pour mémoire, en comparant:

    spam == eggs

est dangereux s'il existe une chance que l'un d'eux soit une instance de modèle différée créée par la requête Model.objects.raw () ou par .defer () appliqué à un QuerySet "normal".

Je mets plus de détails ici: problème Django QuerySet .defer () - bug ou fonctionnalité?

4
Tomasz Zieliński

Comme le commente orokusaki, "si aucune des instances n'a de clé primaire, elle retournera toujours vrai". Si vous voulez que cela fonctionne, vous pouvez étendre votre modèle comme suit:

def __eq__(self, other):
    eq = isinstance(other, self.__class__) and self._get_pk_val() == other._get_pk_val()

    if eq and self._get_pk_val() is None:
        return id(self) == id(other)
    return eq
1
Aidan Fitzpatrick

Il serait étrange que deux instances de modèle soient égales si elles avaient des attributs différents. La plupart du temps, ce ne serait pas souhaitable.

Ce que vous voulez, c'est un cas particulier. Comparant spam.pk == eggs.pk est une bonne idée. S'il n'y a pas encore de pk, car ils n'ont pas été enregistrés, alors il est plus difficile de définir quelles instances sont "vraiment" les mêmes si certains attributs sont différents.

Que diriez-vous d'ajouter un attribut personnalisé à vos instances lors de leur création, par exemple: spam.myid=1, eggs.myid=2

De cette façon, à un moment donné de votre code lorsque spamcopy1.seasoning=ketchup et spamcopy2.seasoning=blackpepper vous pouvez comparer leur attribut myid pour voir s'il s'agit bien du "même" spam.

0
Anentropic