web-dev-qa-db-fra.com

Fonctionnalité de Python `in` vs.` __contains__`

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__?

Épilogue

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.

21
joshua.r.smith

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.

12
Eli Korvigo

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__.

7
knowledge