J'ai implémenté le __contains__
méthode sur une classe pour la première fois l'autre jour, et le comportement n'était pas ce à quoi je m'attendais. Je soupçonne qu'il y a une certaine subtilité dans l'opérateur in
que je ne comprends pas et j'espérais que quelqu'un pourrait m'éclairer.
Il me semble que l'opérateur in
ne se contente pas d'envelopper simplement __contains__
, mais il tente également de forcer la sortie de __contains__
à booléen. Par exemple, considérez la classe
class Dummy(object):
def __contains__(self, val):
# Don't perform comparison, just return a list as
# an example.
return [False, False]
L'opérateur in
et un appel direct à __contains__
la méthode renvoie une sortie très différente:
>>> dum = Dummy()
>>> 7 in dum
True
>>> dum.__contains__(7)
[False, False]
Encore une fois, il semble que in
appelle __contains__
mais en contraignant le résultat à bool
. Je ne trouve ce comportement documenté nulle part, sauf pour le fait que __contains__
documentation dit __contains__
ne doit renvoyer que True
ou False
.
Je suis content de suivre la convention, mais quelqu'un peut-il me dire la relation précise entre in
et __contains__
?
J'ai décidé de choisir la réponse @ eli-korvigo, mais tout le monde devrait regarder @ ashwini-chaudhary commentaire sur le bug , ci-dessous.
Utilisez la source, Luke!
Trouvons la mise en œuvre de l'opérateur in
>>> import dis
>>> class test(object):
... def __contains__(self, other):
... return True
>>> def in_():
... return 1 in test()
>>> dis.dis(in_)
2 0 LOAD_CONST 1 (1)
3 LOAD_GLOBAL 0 (test)
6 CALL_FUNCTION 0 (0 positional, 0 keyword pair)
9 COMPARE_OP 6 (in)
12 RETURN_VALUE
Comme vous pouvez le voir, l'opérateur in
devient l'instruction de machine virtuelle COMPARE_OP
. Vous pouvez trouver cela dans ceval.c
TARGET(COMPARE_OP)
w = POP();
v = TOP();
x = cmp_outcome(oparg, v, w);
Py_DECREF(v);
Py_DECREF(w);
SET_TOP(x);
if (x == NULL) break;
PREDICT(POP_JUMP_IF_FALSE);
PREDICT(POP_JUMP_IF_TRUE);
DISPATCH();
Jetez un œil à l'un des commutateurs de cmp_outcome()
case PyCmp_IN:
res = PySequence_Contains(w, v);
if (res < 0)
return NULL;
break;
Ici, nous avons l'appel PySequence_Contains
int
PySequence_Contains(PyObject *seq, PyObject *ob)
{
Py_ssize_t result;
PySequenceMethods *sqm = seq->ob_type->tp_as_sequence;
if (sqm != NULL && sqm->sq_contains != NULL)
return (*sqm->sq_contains)(seq, ob);
result = _PySequence_IterSearch(seq, ob, PY_ITERSEARCH_CONTAINS);
return Py_SAFE_DOWNCAST(result, Py_ssize_t, int);
}
Cela renvoie toujours un int
(un booléen).
P.S.
Merci à Martijn Pieters d'avoir fourni way pour trouver l'implémentation de l'opérateur in
.
Dans référence Python pour __contains__
il est écrit que __contains__
Doit retourner True
ou False
.
Si la valeur de retour n'est pas booléenne, elle est convertie en booléenne. En voici la preuve:
class MyValue:
def __bool__(self):
print("__bool__ function ran")
return True
class Dummy:
def __contains__(self, val):
return MyValue()
Maintenant, écrivez dans Shell:
>>> dum = Dummy()
>>> 7 in dum
__bool__ function ran
True
Et bool()
de la liste non vide renvoie True
.
Modifier:
Ce n'est que de la documentation pour __contains__
, Si vous voulez vraiment voir une relation précise, vous devriez envisager de chercher dans le code source, même si je ne sais pas exactement où, mais c'est déjà répondu. En documentation pour comparaison il est écrit:
Cependant, ces méthodes peuvent renvoyer n'importe quelle valeur, donc si l'opérateur de comparaison est utilisé dans un contexte booléen (par exemple, dans la condition d'une instruction
if
), Python appellera - bool () sur la valeur pour déterminer si le résultat est vrai ou faux.
Vous pouvez donc deviner que c'est similaire avec __contains__
.