我前几天第一次在课堂上实施了__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
。< / p>
我很高兴遵循惯例,但有人可以告诉我in
和__contains__
之间的确切关系吗?
我决定选择@ eli-korvigo的答案,但是每个人都应该关注下面comment的@ ashwini-chaudhary bug。
答案 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__
应该返回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__
类似。
答案 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