web-dev-qa-db-ja.com

Python `in`と` __contains__`の機能

先日初めてクラスに__contains__メソッドを実装しましたが、その動作は期待したものではありませんでした。 in 演算子には理解できない微妙な点があると思います。誰かに教えてもらえたらと思っていました。

in演算子は、オブジェクトの__contains__メソッドを単純にラップするのではなく、__contains__の出力をブール値に強制しようとするように見えます。たとえば、クラスを考えてみましょう

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

in演算子と__contains__メソッドへの直接呼び出しは、非常に異なる出力を返します。

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

繰り返しますが、in__contains__を呼び出しているようですが、結果をboolに強制変換しています。 __contains__documentation__contains__TrueまたはFalseのみを返すように指示しているという事実を除いて、この動作が文書化されている場所はありません。

規約を守って満足していますが、in__contains__の正確な関係を誰かに教えてもらえますか?

エピローグ

@ eli-korvigoの回答を選択することにしましたが、以下の bug については、@ ashwini-chaudhary comment をご覧ください。

21
joshua.r.smith

ソース、ルークを使用してください!

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
_

ご覧のとおり、in演算子は_COMPARE_OP_仮想マシン命令になります。あなたはそれを 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(); 
_

cmp_outcome()のスイッチの1つを見てください。

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

ここに_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);
}
_

これは常にint(ブール値)を返します。

追伸.

in演算子の実装を見つけるために way を提供してくれたMartijn Pietersに感謝します。

12
Eli Korvigo

___contains___ のPythonリファレンスでは、___contains___がTrueまたはFalseを返すように書かれています。

戻り値がブール値でない場合は、ブール値に変換されます。ここに証明があります:

_class MyValue:
    def __bool__(self):
        print("__bool__ function ran")
        return True

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

今シェルで書いてください:

_>>> dum = Dummy()
>>> 7 in dum
__bool__ function ran
True
_

空でないリストのbool()Trueを返します。

編集:

これは___contains___の唯一のドキュメントです。正確な関係を確認したい場合は、ソースコードを調べることを検討してください。正確な場所はわかりませんが、すでに回答されています。 比較のためのドキュメント と書かれています:

ただし、これらのメソッドは任意の値を返すことができるため、比較演算子がブールコンテキストで使用されている場合(たとえば、ifステートメントの条件で)、Pythonは- bool() 値に基づいて、結果がtrueかfalseかを判断します。

したがって、これは___contains___と似ていると推測できます。

7
knowledge