Changements/mises à jour de Python 3 pour cet algorithme
Comment la __eq__
traitées en Python et dans quel ordre ?
a == b
Il est généralement admis, mais ce n'est pas toujours le cas, que a == b
invoque a.__eq__(b)
o type(a).__eq__(a, b)
.
Explicitement, l'ordre d'évaluation est le suivant :
- si
b
est une sous-classe stricte (pas le même type) de a
et possède un __eq__
l'appelle et renvoie la valeur si la comparaison est mise en œuvre,
- sinon, si
a
a __eq__
l'appelle et le renvoie si la comparaison est mise en œuvre,
- else, see if we didn't call b's
__eq__
et qu'il l'a, puis l'appelle et le renvoie si la comparaison est mise en œuvre,
- enfin, faire la comparaison pour l'identité, la même comparaison que celle de
is
.
Nous savons qu'une comparaison n'est pas mise en œuvre si la méthode renvoie NotImplemented
.
(Dans Python 2, il y avait une fonction __cmp__
qui était recherchée, mais elle a été dépréciée et supprimée dans Python 3).
Testons par nous-mêmes le comportement de la première vérification en laissant B sous-classer A, ce qui montre que la réponse acceptée est erronée sur ce point :
class A:
value = 3
def __eq__(self, other):
print('A __eq__ called')
return self.value == other.value
class B(A):
value = 4
def __eq__(self, other):
print('B __eq__ called')
return self.value == other.value
a, b = A(), B()
a == b
qui n'imprime que B __eq__ called
avant de renvoyer False
.
Notez que je corrige également une petite erreur dans la question où self.value
est comparée à other
au lieu de other.value
- dans cette comparaison, nous obtenons deux objets ( self
y other
), généralement du même type puisque nous ne faisons pas de vérification de type ici (mais ils peuvent être de types différents), et nous avons besoin de savoir s'ils sont égaux. Pour savoir s'ils sont égaux ou non, nous vérifions la valeur de la variable value
ce qui doit être fait pour les deux objets.
Comment connaître cet algorithme complet ?
Les autres réponses semblent incomplètes et obsolètes, c'est pourquoi je vais mettre à jour les informations. y Je vous montrerai comment vous pouvez faire vos propres recherches.
Cette question est traitée au niveau C.
Nous devons examiner deux éléments de code différents : le code par défaut, le code d'accès et le code de sécurité. __eq__
pour les objets de la classe object
et le code qui recherche et appelle la fonction __eq__
qu'elle utilise ou non la méthode par défaut __eq__
ou un modèle personnalisé.
Défaut __eq__
Regarder __eq__
dans le documentation pertinente sur l'api C nous montre que __eq__
est géré par tp_richcompare
- qui, dans la "object"
définition du type dans cpython/Objects/typeobject.c
est défini dans object_richcompare
pour case Py_EQ:
.
case Py_EQ:
/* Return NotImplemented instead of False, so if two
objects are compared, both get a chance at the
comparison. See issue #1393. */
res = (self == other) ? Py_True : Py_NotImplemented;
Py_INCREF(res);
break;
Ainsi, ici, si self == other
nous renvoyons True
sinon nous renvoyons le NotImplemented
objet. C'est le comportement par défaut de toute sous-classe d'objet qui n'implémente pas sa propre fonction __eq__
méthode.
Comment __eq__
est appelé
Nous trouvons ensuite la documentation de l'API C, le document PyObject_RichCompare qui appelle la fonction do_richcompare
.
Nous constatons alors que le tp_richcompare
créée pour la fonction "object"
La définition C est appelée par do_richcompare
Nous allons donc examiner cela de plus près.
La première vérification de cette fonction porte sur les conditions des objets comparés :
- sont no le même type, mais
- le type du second est une sous-classe du type du premier, et
- le type du second a un
__eq__
méthode,
puis appeler la méthode de l'autre avec les arguments échangés, en renvoyant la valeur si elle a été mise en œuvre. Si cette méthode n'est pas implémentée, nous continuons...
if (!Py_IS_TYPE(v, Py_TYPE(w)) &&
PyType_IsSubtype(Py_TYPE(w), Py_TYPE(v)) &&
(f = Py_TYPE(w)->tp_richcompare) != NULL) {
checked_reverse_op = 1;
res = (*f)(w, v, _Py_SwappedOp[op]);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
Ensuite, nous verrons si nous pouvons consulter le __eq__
du premier type et l'appeler. Tant que le résultat n'est pas NotImplemented, c'est-à-dire qu'il est implémenté, nous le renvoyons.
if ((f = Py_TYPE(v)->tp_richcompare) != NULL) {
res = (*f)(v, w, op);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
Sinon, si nous n'avons pas essayé la méthode de l'autre type et qu'elle existe, nous l'essayons, et si la comparaison est implémentée, nous la renvoyons.
if (!checked_reverse_op && (f = Py_TYPE(w)->tp_richcompare) != NULL) {
res = (*f)(w, v, _Py_SwappedOp[op]);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
}
Enfin, nous disposons d'une solution de repli au cas où elle ne serait pas mise en œuvre pour l'un ou l'autre type.
La solution de repli vérifie l'identité de l'objet, c'est-à-dire s'il s'agit du même objet au même endroit dans la mémoire. self is other
:
/* If neither object implements it, provide a sensible default
for == and !=, but raise an exception for ordering. */
switch (op) {
case Py_EQ:
res = (v == w) ? Py_True : Py_False;
break;
Conclusion
Dans une comparaison, nous respectons d'abord l'implémentation de la sous-classe de comparaison.
Nous tentons ensuite la comparaison avec l'implémentation du premier objet, puis avec celle du second si elle n'a pas été appelée.
Enfin, nous utilisons un test d'identité pour comparer l'égalité.