【问题标题】:How is the __contains__ method of the list class in Python implemented?Python中list类的__contains__方法是如何实现的?
【发布时间】:2017-06-16 20:15:18
【问题描述】:

假设我定义了以下变量:

mode = "access"
allowed_modes = ["access", "read", "write"]

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

assert any(mode == allowed_mode for allowed_mode in allowed_modes)

但是,我似乎可以简单地将其替换为

assert mode in allowed_modes

根据ThiefMasterPython List Class __contains__ Method Functionality中的回答,这两者应该是等价的。真的是这样吗?我如何通过查找 Python 的源代码轻松验证这一点?

【问题讨论】:

标签: python


【解决方案1】:

不,它们不相等。例如:

>>> mode = float('nan')
>>> allowed_modes = [mode]
>>> any(mode == allowed_mode for allowed_mode in allowed_modes)
False
>>> mode in allowed_modes
True

请参阅Membership test operations 了解更多详情,包括此声明:

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

【讨论】:

    【解决方案2】:

    Python 列表在 C 代码中定义。

    您可以通过查看code in the repository 来验证它:

    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;
    }
    

    很明显,这段代码循环遍历列表中的项目并在elPyList_GET_ITEM(a, i) 之间的第一个相等(Py_EQ)比较返回 1 时停止。

    【讨论】:

    • 我发现这具有误导性,因为您让它看起来只检查了“平等”,因此 OP 的两个 sn-ps 将是等效的。他们不是,因为 identity 也被检查了,这很重要。请参阅我的回答和PyObject_RichCompareBool 的注释“如果 o1 和 o2 是同一个对象,PyObject_RichCompareBool() 将始终为 Py_EQ 返回 1,为 Py_NE 返回 0”。
    【解决方案3】:

    不等价,因为 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
    >>> 
    

    这比方法本身的 python 源更具指导性,但 here__contains__ 的源用于列表,并且循环在 C 中,这可能比 Python 循环更快。

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

    >>> import timeit
    >>> timeit.timeit(f1)
    0.18974408798385412
    >>> timeit.timeit(f2)
    0.7702703149989247
    >>> 
    

    【讨论】:

    • “不等价”取决于您如何定义“等价”。功能上,两种解决方案等效的,因为它们将产生相同的结果。
    • 没错,但我的目的是尽可能多地发现差异并突出它们。之后,您可以丢弃那些不相关的,然后考虑两个等价或其他。对我来说,在这种情况下你应该使用 f1 而不是 f2 是很清楚的。
    • 我很清楚你的意图;) - 只是想明确两种解决方案确实产生相同的结果(因为 OP 没有定义“等效”)并且两者都会进行顺序查找与平等测试。当然,遏制测试是显而易见的 Pythonic 解决方案(因为它是最快的)。
    • @brunodesthuilliers 他们并不总是产生相同的结果。
    猜你喜欢
    • 2021-09-23
    • 2016-10-10
    • 1970-01-01
    • 1970-01-01
    • 2021-09-28
    • 1970-01-01
    • 2011-04-24
    相关资源
    最近更新 更多