Python`in`与`__contains__`的功能

时间:2016-07-23 13:54:30

标签: python python-2.7 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__应该只返回TrueFalse。< / p>

我很高兴遵循惯例,但有人可以告诉我in__contains__之间的确切关系吗?

后记

我决定选择@ eli-korvigo的答案,但是每个人都应该关注下面comment的@ ashwini-chaudhary bug

3 个答案:

答案 0 :(得分:12)

使用来源,卢克!

让我们追溯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()

中的一个开关
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(布尔值)。

P.S。

感谢Martijn Pieters提供way查找in运算符的实现。

答案 1 :(得分:7)

Python reference for __contains__中写明__contains__应该返回TrueFalse

如果返回值不是布尔值,则将其转换为布尔值。这是证据:

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

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

现在写在shell中:

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

非空列表的bool()会返回True

修改

它是__contains__的唯一文档,如果你真的想看到精确的关系,你应该考虑查看源代码,虽然我不确定究竟在哪里,但它已经是回答。在documentation for comparison中写了:

  

但是,这些方法可以返回任何值,因此如果在布尔上下文中使用比较运算符(例如,在if语句的条件下),Python将在值上调用bool()确定结果是真还是假。

因此,您可以猜测它与__contains__类似。

答案 2 :(得分:1)

这是为了让阅读本文的任何人了解使用哪个,我会说使用 __contains__() 而不是 in,因为它更快

为了验证这一点,我做了一个简单的实验。

import time
startTime = time.time()
q = 'abababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababc'

print(q.__contains__('c'))
#print('c' in q)
endTime = time.time()
deltaTime = endTime - startTime
print(deltaTime)

在一次迭代中,我评论了 in,而其他时候,我评论了 __contains__。结果如下:

(Using in)
PS C:\Users\username> & python c:/Users/username/containsvsin.py
True
0.0009970664978027344
(Using __contains__)
PS C:\Users\username> & python c:/Users/username/Downloads/containsvsin.py
True
0.0