Python中`in`和`__contains__`的功能区别

24
我前几天第一次在一个类上实现了__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]
< p > in运算符和直接调用__contains__方法返回非常不同的输出:

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

再次看起来好像是in调用了__contains__,但是将结果强制转换为bool。我找不到这种行为的文档,除了__contains__文档__contains__只应该返回TrueFalse之外。

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

结语

我决定选择@eli-korvigo的答案,但是每个人都应该查看@ashwini-chaudhary在下面的评论中关于错误的说明。


因为你的contains方法返回等同于bool([False, False])的值。 - michael-tkach
3
相关问题:in 应该与 __contains__ 的返回值一致 - Ashwini Chaudhary
1
@AshwiniChaudhary:你能把这个评论写成一个答案吗?只需要一句话就可以了。我以前从未见过这个错误报告,它恰好回答了我的问题。我不太关心in的具体实现,而是关心设计理念和明显缺乏文档。如果你发布这个答案,我会选择你的答案作为被接受的答案。 - joshua.r.smith
3个回答

19

使用原始代码,卢克!

让我们追踪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提供了查找in运算符实现方式的方法


1
谢谢你详细的回答,但我更想知道in的设计背后的原因和缺乏文档的表现,而不是它的实现方式。无论如何,我还是会给你的回答点赞,因为它提供了有用的信息。 - joshua.r.smith
@joshua.r.smith 我猜,在这种情况下,实现直接与推理相关。基本上,这就是Python-C API的构思方式。至于缺乏文档,文档并没有真正引用“True”或“False”,它们只说__cointains__应该返回一些东西,要么为true,要么为false(即可以评估为“True”或“False”)。您可以在文档中看到,他们明确使用TrueFalse。无论如何,他们本可以写得不那么含糊,因此您可以提交文档补丁报告。 - Eli Korvigo

8
Python参考手册中的__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__的文档说明,如果您真的想要看到精确的关系,您应该考虑查看源代码,尽管我不确定在哪里,但已经有答案了。在比较的文档中写道:

然而,这些方法可以返回任何值,所以如果比较运算符在布尔上下文中使用(例如,在if语句的条件中),Python将调用bool()函数来确定结果是true还是false。

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


2
我认为“__bool__函数运行”应该是“__bool__函数运行”。 - Jon

-3
这是给任何阅读此文的人理解哪个更好用的建议,我会说使用__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

2
这并没有回答楼主的问题。 - ZzZombo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接