【问题标题】:Python In Operator - Short CircuitPython In 运算符 - 短路
【发布时间】:2018-06-14 17:24:58
【问题描述】:

我在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

有没有详细解释为什么9990 花费的时间更长。 in 操作符像循环吗?

另外,有没有办法告诉in 运算符在找到值后“停止循环”(或者这是我没有看到的已经默认的行为)?

最后 - 是否有另一个我正在跳过的运算符/函数与我所说的“短路”in 相关?

【问题讨论】:

  • Is the in operator like a loop? 是的,当您使用列表调用 in 时,我相信会调用 C 循环。
  • 另外,in 是条件运算符,不是逻辑运算符,因此此处不适用“短路”。
  • @cᴏʟᴅsᴘᴇᴇᴅ,啊!好的。当我谷歌它时,我在文档中找不到它。您对此有任何引用吗?
  • in list 将遍历列表直到找到元素。如果这就是你的意思,那么它就是短路的:一旦找到元素,它就不会继续查看列表。
  • 我认为this answer 也可能很有趣(这与您要转换为list 的问题略有不同,但它是关于@987654335 如何进行的非常有趣的解释@作品)

标签: python python-internals short-circuiting


【解决方案1】:

确实会发生短路。 in 运算符调用 __contains__ 方法,而每个类又以不同的方式实现该方法(在您的情况下为 list)。搜索999 花费的时间大约是搜索0 的两倍,因为一半的工作是创建列表,另一半是迭代它,这在0 的情况下是短路的。

【讨论】:

  • 啊!所以时间翻倍是因为list(range(x)) 被设置了?
  • 是的。就是这样。
  • which in turn is implemented differently per class - 你能详细说明一下吗?我觉得这个声明中包含了一些重要的东西!
  • 创建新类时,Python 支持运算符重载。 a = A() + 5 等语句可以通过实现A.__add__(self, other) 来工作。同样,当 Python 遇到 x in y 时,它会检查 ys 类是否实现了 __contains__ method,如果是,则调用 y.__contains__(x)。另一方面,如果没有实现该方法,则会抛出 TypeError 异常。
  • @StefanPochmann,您的评论是正确的,我的解释是简化版。如果__contains__ 没有实现,那么它将尝试遍历对象,寻找请求的值。这意味着实现__iter__ 或什至只是__getitem__ 就足够了。详情见这里:docs.python.org/3/reference/datamodel.html#object.__contains__.
【解决方案2】:

inlist 对象的实现见list_contains。它对列表执行扫描,如果最后一个比较找到了元素,它会提前退出,继续在那里没有意义。

涉及的循环是:

for (i = 0, cmp = 0 ; cmp == 0 && i < Py_SIZE(a); ++i)
    cmp = PyObject_RichCompareBool(el, PyList_GET_ITEM(a, i),
                                       Py_EQ);

如果cmp1(从PyObject_RichCompareBool 返回的匹配值),则for 循环条件(cmp == 0 &amp;&amp; i &lt; Py_SIZE(a)) 变为假并终止。

对于内置的列表对象,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 操作都会产生相似的时序。

【讨论】:

  • 当我将list() 包裹在range() 周围时,我相信我创造了一个红鲱鱼。已经定义了l,这可以清除它! in 是否根据in 运算符的后半部分为不同的数据类型调用不同的“函数”? (如果我使用了错误的术语,请原谅我的错误命名)
  • @MattR 是的,确实如此。您可以查看参考手册中的Membership test operations,了解所称内容的完整概要(这对于包含评论来说太多了)。
  • 我非常感谢文档的链接。我会再读一些。
  • 不客气@MattR。值得庆幸的是,Python 的文档非常好且可读。值得一读。
  • 它还可以单独为“范围”对象计时,而不将其转换为列表。
【解决方案3】:

这是另一个带有哈希对象的外观,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 实现必须执行顺序搜索。集合包含使用哈希值,与在检查的第一个元素中查找项目相当。

【讨论】:

  • in 在集合中比在列表中更快,但它在短路方面显示了什么?
  • 我很欣赏优化作品。
  • @Loquacious:这只是用列表突出了早期循环退出的效果; set in 根本不会短路,因为引用是直接的。
猜你喜欢
  • 1970-01-01
  • 2012-07-11
  • 2012-02-04
  • 2020-05-09
  • 2019-02-04
相关资源
最近更新 更多