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__
应该只返回True
或False
之外,我在任何地方都找不到这种行为。
我很高兴遵守约定,但有人能告诉我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__
应该返回True
或False
。
如果返回值不是布尔值,则将其转换为布尔值。这是证据:
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 的构思方式。至于缺乏文档,文档并没有真正引用True
或False
,他们只是说__cointains__
应该返回真或假(即可以评估为True
或False
)。您可以在整个文档中看到,它们在重要的地方明确使用了True
和False
。无论如何,他们可以写得不那么含糊,所以你可以提交一份文档补丁报告。【参考方案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] Find available methods and help in REPL