Python `in` 与 `__contains__` 的功能

Posted

技术标签:

【中文标题】Python `in` 与 `__contains__` 的功能【英文标题】:Functionality of Python `in` vs. `__contains__` 【发布时间】:2016-11-27 07:22:57 【问题描述】:

前几天我第一次在一个类上实现了__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 之外,我在任何地方都找不到这种行为。

我很高兴遵守约定,但有人能告诉我in__contains__ 之间的确切关系吗?

结语

我决定选择@eli-korvigo 答案,但大家应该看看@ashwini-chaudhary comment 关于bug,下面。

【问题讨论】:

因为你的 contains-method 返回等效于 bool([False, False]) 相关bug:in should be consistent with return value of __contains__ @AshwiniChaudhary:你能写下这个评论作为答案吗?就一个单线左右就可以了。我从未见过这个错误报告,它准确地回答了我的问题。我不太关心in 的具体实现,因为我关心设计推理和明显缺乏文档。如果您发布此答案,我将选择您的答案作为接受的答案。 【参考方案1】:

在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__很相似。

【讨论】:

我认为"__bool__ function runned" 应该是"__bool__ function ran"【参考方案2】:

使用来源,卢克!

让我们追溯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(一个布尔值)。

附言

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

【讨论】:

感谢您的详尽回答,但我一直在寻找设计背后的更多理由以及明显缺乏文档而不是 in 的实现。无论如何我都赞成你的回答,因为它是有用的信息。 @joshua.r.smith 我猜,在这种情况下,实现与推理直接相关。基本上,这就是 Python-C API 的构思方式。至于缺乏文档,文档并没有真正引用TrueFalse,他们只是说__cointains__ 应该返回真或假(即可以评估为TrueFalse )。您可以在整个文档中看到,它们在重要的地方明确使用了TrueFalse。无论如何,他们可以写得不那么含糊,所以你可以提交一份文档补丁报告。【参考方案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

【讨论】:

这不能回答 OP 的问题。

以上是关于Python `in` 与 `__contains__` 的功能的主要内容,如果未能解决你的问题,请参考以下文章

python 定义class时的内置方法

[Python] Find available methods and help in REPL

python手记------字典(操作方法)

我无法理解 python 中的 __contains__ 方法

python数据类型

Python __len__()__reversed__()__contains__()