我正在Short-Circuiting in Python上阅读一篇有趣的帖子,并想知道in
运算符是否属实。我的简单测试将得出结论:它没有:
%%timeit -n 1000
0 in list(range(10))
1000 loops, best of 3: 639 ns per loop
%%timeit -n 1000
0 in list(range(1000))
1000 loops, best of 3: 23.7 µs per loop
# larger the list, the longer it takes. however, i do notice that a higher
# value does take longer.
%%timeit -n 1000
999 in list(range(1000))
1000 loops, best of 3: 45.1 µs per loop
是否详细解释了为什么999
需要的时间超过0
。 in
运算符是否像循环一样?
另外,有没有办法告诉in
运营商"停止循环"一旦找到价值(或者这是我没见过的已经违约的行为)?
最后 - 是否还有另一个我正在跳过的操作员/功能,这就是我所说的关于"短路" in
?
答案 0 :(得分:3)
确实发生了短路。 in
运算符调用__contains__
方法,而每个类的实现方式不同(在您的情况下为list
)。搜索999
需要大约两倍的时间来搜索0
,因为一半的工作正在创建列表,而另一半正在迭代它,在{{0
的情况下会被短路。 1}}。
答案 1 :(得分:2)
在list_contains
中可以找到in
个list
对象的实现。它执行列表扫描并在最后一次比较找到元素时提前退出,继续那里没有意义。
涉及的循环是:
for (i = 0, cmp = 0 ; cmp == 0 && i < Py_SIZE(a); ++i)
cmp = PyObject_RichCompareBool(el, PyList_GET_ITEM(a, i),
Py_EQ);
如果cmp
为1
(匹配时从PyObject_RichCompareBool
返回的值),则for
循环条件(cmp == 0 && i < Py_SIZE(a)
)将变为false并终止。
对于内置的列表对象,in
的调用是C
函数(对于CPython)。对于Python的其他实现,这可以是使用不同语言结构的不同语言。
对于Python中的用户定义类,在参考手册的Membership test operations中定义了所调用的内容,看看那里有一个被调用的内容。
你也可以通过计时来得出这个结论:
l = [*range(1000)]
%timeit 1 in l
85.8 ns ± 11.9 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
%timeit 999 in l
22 µs ± 221 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
元素越远,您需要扫描的越多。如果它没有短路,则所有in
操作都会产生类似的时序。
答案 2 :(得分:0)
这是使用散列对象set
的另一种外观:
from time import time
qlist = list(range(1000))
qset = set(qlist)
start = time()
for i in range(1000):
0 in qlist
print time() - start
start = time()
for i in range(1000):
999 in qlist
print time() - start
start = time()
for i in range(1000):
0 in qset
print time() - start
start = time()
for i in range(1000):
999 in qset
print time() - start
输出:
0.000172853469849 0 in list
0.0399038791656 999 in list
0.000147104263306i 0 in set
0.000195980072021 999 in set
正如其他人所说,list
实施必须进行顺序搜索。设置包含使用散列值,与查找第一个元素中的项目相同。