38

我指的是这个问题,尤其是@David Robinson 和@mgilson 对第一个答案的评论: Sum the second value of each tuple in a list

最初的问题是对每个管的第二个值求和:

structure = [('a', 1), ('b', 3), ('c', 2)]

第一个答案:

sum(n for _, n in structure)

第二个答案:

sum(x[1] for x in structure)

根据讨论,第一个答案要快 50%。

一旦我弄清楚第一个答案是做什么的(来自 Perl,我用谷歌搜索了 python 中的特殊 _ 变量意味着什么),我想知道为什么会出现一个纯子集任务(只获取每个元组的第二个元素而不是获取并将两个元素绑定到变量中)实际上更慢?是否错过了在 Python 中优化索引访问的机会?我错过了第二个答案需要时间的东西吗?

4

2 回答 2

37

如果你看一下 python 字节码,很快就会明白为什么解包速度更快:

>>> import dis
>>> def unpack_or_index(t=(0, 1)):
...     _, x = t
...     x = t[1]
... 
>>> dis.dis(unpack_or_index)
  2           0 LOAD_FAST                0 (t)
              3 UNPACK_SEQUENCE          2
              6 STORE_FAST               1 (_)
              9 STORE_FAST               2 (x)

  3          12 LOAD_FAST                0 (t)
             15 LOAD_CONST               1 (1)
             18 BINARY_SUBSCR       
             19 STORE_FAST               2 (x)
             22 LOAD_CONST               0 (None)
             25 RETURN_VALUE        

元组解包操作是一个简单的字节码 ( UNPACK_SEQUENCE),而索引操作必须调用元组 ( BINARY_SUBSCR) 上的方法。解包操作可以在 python 评估循环中内联发生,而订阅调用需要在元组对象上查找函数以检索值,使用PyObject_GetItem.

UNPACK_SEQUENCE操作码源代码特殊情况下,序列长度与参数长度完全匹配的 python 元组或列表解包:

        if (PyTuple_CheckExact(v) &&
            PyTuple_GET_SIZE(v) == oparg) {
            PyObject **items = \
                ((PyTupleObject *)v)->ob_item;
            while (oparg--) {
                w = items[oparg];
                Py_INCREF(w);
                PUSH(w);
            }
            Py_DECREF(v);
            continue;
        } // followed by an "else if" statement for a list with similar code

上面的代码进入元组的原生结构并直接检索值;无需使用繁重的调用,例如PyObject_GetItem必须考虑到对象可能是自定义 python 类。

BINARY_SUBSCR操作码仅针对 python列表进行了优化;任何不是本机 python 列表的东西都需要PyObject_GetItem调用。

于 2012-10-23T06:19:28.010 回答
18

索引通过__getitem__特殊方法进行,因此必须对每个项目进行函数查找和执行。这意味着对于n项目列表,您最终会进行n查找/调用。

使用本地列表/元组时,解包不必处理它;它只是通过__iter__单个调用,然后在 C 中解压缩结果序列。

于 2012-10-23T06:11:52.103 回答