为什么'in'比`__contains__`更快?

时间:2016-07-15 15:52:48

标签: python

我认为in只是__contains__

的一个清单
In [1]: li = [1, 2, 3, 4, 5]

In [2]: 4 in li
Out[2]: True

In [3]: li.__contains__(4)
Out[3]: True

然而in快2倍

In [4]: %timeit 4 in li
10000000 loops, best of 3: 103 ns per loop

In [5]: %timeit li.__contains__(4)
The slowest run took 10.06 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 213 ns per loop

你能解释两者之间的差异吗?为什么in更快?

3 个答案:

答案 0 :(得分:5)

可能与{}dict()快的原因相同。方法调用引入了额外的开销:

>>> from dis import dis
>>> li = [1, 2, 3, 4, 5]
>>> c = lambda: 4 in li
>>> d = lambda: li.__contains__(4)
>>> dis(c)
  1           0 LOAD_CONST               1 (4)
              3 LOAD_GLOBAL              0 (li)
              6 COMPARE_OP               6 (in)
              9 RETURN_VALUE
>>> dis(d)
  1           0 LOAD_GLOBAL              0 (li)
              3 LOAD_ATTR                1 (__contains__)
              6 LOAD_CONST               1 (4)
              9 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             12 RETURN_VALUE

在后一种情况下请参阅CALL_FUNCTION

答案 1 :(得分:2)

这个答案有点建立在@MosesKoledoye的解释之上,但没有任何反汇编的源代码。

.__contains__比使用in慢的原因有多种原因:

  • 每次使用.访问方法/函数/类/属性时,都会在类/实例字典中引入查找。这引入了一些开销。

  • 对于自定义类,对in的调用会转换为.__contains__(或.__iter__),但对于所有python内置函数来说,这不一定都是正确的。 Python列表以C语言实现,因此something in list可以转换为C函数,而无需调用__contains____iter__

  • in list直接转发到C代码(COMPARE_OP),这比访问python函数然后调用C代码(CALL_FUNCTION)快得多。

一般情况下:对于使用像in这样的文字的python内置函数可能会更快,但这对于自定义类来说(通常)不正确。 python中的大多数数据模型都适用于自定义类。 list.__contains__可能只是为了适应数据模型约定而实现,而不是因为它需要或更快。

答案 2 :(得分:2)

在Python的标准实现中,Button并没有直接使用in__contains__实际上在结构中使用C级in函数指针来表示对象的类型。对于在Python中实现sq_contains的类型,__contains__指向查找并调用sq_contains方法的函数,但对于在C中实现__contains__的类型,{ {1}}直接指向方法的C实现。

由于__contains__是在C中实现的,sq_contains可以直接调用C实现,而不需要经过Python方法查找和Python函数调用的开销。 list.__contains__必须执行Python方法查找和Python函数调用,因此速度要慢得多。

如果要查看4 in li涉及的代码路径,可以在字节码评估循环中从COMPARE_OP开始跟随调用层次结构。您会看到它使用cmp_outcome

li.__contains__(4)

cmp_outcome使用PySequence_Contains

4 in li

PySequence_Contains查找代表列表类型的C结构的TARGET(COMPARE_OP) { w = POP(); v = TOP(); if (PyInt_CheckExact(w) && PyInt_CheckExact(v)) { ... } else { slow_compare: x = cmp_outcome(oparg, v, w); } 字段的static PyObject * cmp_outcome(int op, register PyObject *v, register PyObject *w) { int res = 0; switch (op) { ... case PyCmp_IN: res = PySequence_Contains(w, v); if (res < 0) return NULL; break; 字段:

sq_contains

This field存储一个函数指针list_contains,C函数实现列表包含检查。 Python不得不执行任何dict查找来查找方法,或者分配方法对象来表示方法,或者构建一个参数元组以传递给方法。