Python中列表类的__contains__方法是如何实现的?

Kur*_*eek 3 python

假设我定义了以下变量:

mode = "access"
allowed_modes = ["access", "read", "write"]
Run Code Online (Sandbox Code Playgroud)

我目前有一个类型检查语句

assert any(mode == allowed_mode for allowed_mode in allowed_modes)
Run Code Online (Sandbox Code Playgroud)

但是,似乎我可以简单地替换它

assert mode in allowed_modes
Run Code Online (Sandbox Code Playgroud)

根据ThiefMasterPython List Class __contains__ Method Functionality中的回答,这两个应该是等价的.确实如此吗?我怎样才能通过查找Python的源代码轻松验证这一点?

Ste*_*ann 9

不,他们不相同.例如:

>>> mode = float('nan')
>>> allowed_modes = [mode]
>>> any(mode == allowed_mode for allowed_mode in allowed_modes)
False
>>> mode in allowed_modes
True
Run Code Online (Sandbox Code Playgroud)

有关详细信息,请参阅成员资格测试操作,包括此声明

对于容器类型,例如list,tuple,set,frozenset,dict或collections.deque,表达式x in y等效于any(x is e or x == e for e in y).


Łuk*_*ski 7

Python列表在C代码中定义.

您可以通过查看存储库中的代码来验证它:

static int
list_contains(PyListObject *a, PyObject *el)
{
    Py_ssize_t i;
    int cmp;

    for (i = 0, cmp = 0 ; cmp == 0 && i < Py_SIZE(a); ++i)
        cmp = PyObject_RichCompareBool(el, PyList_GET_ITEM(a, i),
                                           Py_EQ);
    return cmp;
}
Run Code Online (Sandbox Code Playgroud)

可以相当直接地看到这段代码循环遍历列表中的项目并在第一次相等(Py_EQ)之间进行比较elPyList_GET_ITEM(a, i)返回1 时停止.

  • 我发现这具有误导性,因为你让它看起来只检查了“相等”,因此OP的两个片段是等效的。但事实并非如此,因为“身份”也会受到检查,这很重要。请参阅我的答案和 [`PyObject_RichCompareBool`](https://docs.python.org/3/c-api/object.html#c.PyObject_RichCompareBool) 的注释,其中写着“如果 o1 和 o2 是同一个对象,则 PyObject_RichCompareBool( ) 将始终为 Py_EQ 返回 1,为 Py_NE 返回 0”。 (2认同)

Nou*_*him 5

这并不等效,因为any需要额外的函数调用,生成器表达式和其他东西。

>>> mode = "access"
>>> allowed_modes =["access", "read", "write"]
>>> 
>>> def f1():
...    mode in allowed_modes
... 
>>> def f2():
...    any(mode == x for x in allowed_modes)
... 
>>> 
>>> 
>>> import dis
>>> dis.dis
dis.dis(          dis.disassemble(  dis.disco(        dis.distb(        
>>> dis.dis(f1)
  2           0 LOAD_GLOBAL              0 (mode)
              3 LOAD_GLOBAL              1 (allowed_modes)
              6 COMPARE_OP               6 (in)
              9 POP_TOP
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE
>>> dis.dis(f2)
  2           0 LOAD_GLOBAL              0 (any)
              3 LOAD_CONST               1 (<code object <genexpr> at 0x7fb24a957540, file "<stdin>", line 2>)
              6 LOAD_CONST               2 ('f2.<locals>.<genexpr>')
              9 MAKE_FUNCTION            0
             12 LOAD_GLOBAL              1 (allowed_modes)
             15 GET_ITER
             16 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             19 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             22 POP_TOP
             23 LOAD_CONST               0 (None)
             26 RETURN_VALUE
>>> 
Run Code Online (Sandbox Code Playgroud)

这比方法本身的python源代码更具启发性,但是这里__contains__for列表的源代码,并且循环在C中进行,这可能比Python循环要快。

一些计时数字证实了这一点。

>>> import timeit
>>> timeit.timeit(f1)
0.18974408798385412
>>> timeit.timeit(f2)
0.7702703149989247
>>> 
Run Code Online (Sandbox Code Playgroud)