2016-07-23 18 views
10

Ich habe die Methode zum ersten Mal am anderen Tag implementiert, und das Verhalten war nicht das, was ich erwartet hatte. Ich vermute, es gibt etwas Subtilität für den Operator, den ich nicht verstehe, und ich hoffte, dass jemand mich erleuchten könnte.Funktionalität von Python `in` vs.` __contains__`

Es scheint mir, dass der Operator in nicht einfach die Methode eines Objekts umschließt, aber es versucht auch, die Ausgabe von auf Boolean zu erzwingen. Betrachten wir zum Beispiel die Klasse

class Dummy(object): 
    def __contains__(self, val): 
     # Don't perform comparison, just return a list as 
     # an example. 
     return [False, False] 

Der in Operator und einen direkten Aufruf der __contains__ Methode Rückkehr sehr unterschiedlichen Ausgangs:

>>> dum = Dummy() 
>>> 7 in dum 
True 
>>> dum.__contains__(7) 
[False, False] 

Auch hier sieht es aus wie in wird __contains__ Aufruf aber dann das Ergebnis Nötigung zu bool. Ich kann dieses Verhalten nirgends außer der Tatsache dokumentiert finden, dass die documentation sagt sollte immer nur True oder False zurückgeben.

Ich bin glücklich, die Konvention zu folgen, aber kann mir jemand die genaue Beziehung zwischen in und sagen?

Epilog

entschied ich @ eli-korvigo Antwort zu wählen, aber jeder an @ ashwini-chaudhary comment über die bug unten aussehen sollte.

+0

Da Ihre contains-Methode das Äquivalent von bool ([False, False]) zurückgibt – x1Mike7x

+2

Zugehöriger Fehler: ['in' sollte konsistent sein mit dem Rückgabewert von' __contains__'] (https://bugs.python.org/issue16011) –

+1

@AshwiniChaudhary: Können Sie diesen Kommentar als Antwort schreiben? Nur ein Einzeiler oder so ist in Ordnung. Ich habe diesen Fehlerbericht noch nie gesehen und er beantwortet meine Frage genau. Ich kümmere mich nicht so sehr um die spezifische Umsetzung von "in", da mir das Design-Argument und der offensichtliche Mangel an Dokumentation wichtig sind. Wenn Sie diese Antwort posten, wähle ich Ihre Antwort als die akzeptierte aus. –

Antwort

8

Verwenden Sie die Quelle, Luke!

Spur Lassen Sie sich auf der in Operator Umsetzung

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

Wie Sie sehen können, der in Operator wird der COMPARE_OP virtuelle Maschinenbefehl. Sie können, dass 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(); 

einen Blick in cmp_outcome() an einem der Schalter Nehmen Sie in finden

case PyCmp_IN: 
    res = PySequence_Contains(w, v); 
    if (res < 0) 
     return NULL; 
    break; 

Hier haben wir die PySequence_Contains Anruf

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); 
} 

, die immer eine int gibt (a Boolesch).

P.S.

Dank Martijn Pieters für die way zur Verfügung stellen, um die Implementierung des in Betreibers zu finden.

+0

Danke für die gründliche Antwort, aber ich suchte mehr nach den Gründen hinter dem Design und dem offensichtlichen Mangel an Dokumentation als die Implementierung von 'in'. Ich stimme deine Antwort trotzdem ab, weil es nützliche Informationen sind. –

+0

@ joshua.r.smith Ich denke, in diesem Fall ist die Implementierung direkt auf die Argumentation bezogen. Im Grunde wurde Python-C API so konzipiert. Was den Mangel an Dokumentation anbelangt, verweisen die Dokumente nicht wirklich auf "True" oder "False", sie sagen nur, dass "__cointains__" etwas entweder wahr oder falsch zurückgeben sollte (dh das kann als "Wahr" oder "Falsch" gewertet werden)). Sie können in den Dokumenten sehen, dass sie explizit 'True' und' False' verwenden, wo es wichtig ist. Wie auch immer, sie könnten es weniger zweideutig geschrieben haben, also können Sie einen Bericht über den Dokumentationspatch einreichen. –

5

In Python reference for __contains__ wird geschrieben, dass True oder False zurückgeben sollte.

Wenn der Rückgabewert nicht boolesch ist, wird er in Boolean konvertiert. Hier ist der Beweis:

class MyValue: 
    def __bool__(self): 
     print("__bool__ function runned") 
     return True 

class Dummy: 
    def __contains__(self, val): 
     return MyValue() 

Jetzt in der Schale schreiben:

>>> dum = Dummy() 
>>> 7 in dum 
__bool__ function runned 
True 

Und bool() von nicht leerer Liste zurück True.

Edit:

Es ist nur Dokumentation für __contains__, wenn Sie wirklich präzise Beziehung wollen sehen Sie auf der Suche in den Quellcode in Betracht ziehen sollten, obwohl ich nicht sicher bin, wo genau, aber es ist schon beantwortet. In documentation for comparison ist es geschrieben:

Allerdings sind diese Methoden einen Wert zurückgeben kann, so dass, wenn der Vergleichsoperator in einem Booleschen Kontext verwendet wird (zB in den Zustand eines if-Anweisung) wird Python bool() auf den Wert nennen um festzustellen, ob das Ergebnis wahr oder falsch ist.

So können Sie vermuten, dass es ähnlich ist mit .