Python列表索引效率

Mad*_*May 13 python performance list

关于内置python列表对象的快速问题.假设您有一个数字0到99的列表.您正在编写一个程序,它接受列表中的最后一项并将其用于其他目的.使用list [-1]比使用list [99]更有效吗?换句话说,在任何一种情况下,python都会遍历整个列表吗?

谢谢你的帮助.

kin*_*all 15

Python不会遍历列表来查找特定索引.列表是连续内存中的数组(指向元素的指针),因此定位所需元素始终是一个简单的乘法和加法.如果有的话,list[-1]会稍微慢一点,因为Python需要将负索引添加到长度以获得真正的索引.(我怀疑它明显变慢了,因为无论如何都是在C中完成的.)

  • @JonClements由程序员知道.显然列表对象知道.但有点人认为`lst [-1]`会慢一点,但如果你知道这个列表有100个元素,你只能写`lst [99]`. (5认同)
  • "如果有的话,list [-1]稍慢一些,因为Python需要获取列表的长度并为其添加负索引." 如果列表是已知的常量长度,则为真.但通常你不会写`lst [99]`并且无论如何都要调用`len()`. (3认同)
  • @David - 列表总是一个已知的常量长度 - 否则它不是列表 (3认同)

mgi*_*son 6

为什么不试试看?

import timeit
t=timeit.timeit('mylist[99]',setup='mylist=list(range(100))',number=10000000)
print (t)
t=timeit.timeit('mylist[-1]',setup='mylist=list(range(100))',number=10000000)
print (t)
Run Code Online (Sandbox Code Playgroud)

当然,正如你可以通过运行几次看到的那样,由于其他答案中指出的原因,实际上没有(明显的)差异.


Sam*_*lar 6

你可以计时:

>>> import timeit
>>> timeit.Timer('values[99]', 'values = range(100)').timeit(number = 10**7)
0.44513392448425293
>>> timeit.Timer('values[99]', 'values = range(100)').timeit(number = 10**7)
0.45273900032043457
>>> timeit.Timer('values[-1]', 'values = range(100)').timeit(number = 10**7)
0.44431495666503906
>>> timeit.Timer('values[-1]', 'values = range(100)').timeit(number = 10**7)
0.44684290885925293
>>> timeit.Timer('values[-1]', 'values = range(100)').timeit(number = 10**7)
0.44867610931396484
>>> timeit.Timer('values[-1]', 'values = range(100)').timeit(number = 10**8)
4.4455509185791016
>>> timeit.Timer('values[99]', 'values = range(100)').timeit(number = 10**8)
4.4184651374816895
>>> timeit.Timer('values[99]', 'values = range(100)').timeit(number = 10**8)
4.4276700019836426
>>> timeit.Timer('values[-1]', 'values = range(100)').timeit(number = 10**8)
4.4026989936828613
>>> timeit.Timer('values[-1]', 'values = range(100)').timeit(number = 10**8)
4.4386618137359619
>>> timeit.Timer('values[99]', 'values = range(100)').timeit(number = 10**8)
4.3991479873657227
>>> 
Run Code Online (Sandbox Code Playgroud)

确实没有区别,但如果您确实想要最后一个项目values[-1]似乎是最简单/最安全的方法,因为无论列表的长度如何,只要它不是空列表,它总是会获取最后一个项目,

这会引发异常:

>>> [][-1]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range
>>> 
Run Code Online (Sandbox Code Playgroud)

换句话说,无论哪种情况,python 都会迭代整个列表吗?

在这两种情况下,python 都不会遍历列表。

我实际上很好奇 python 是否做了什么不同的事情,所以我反汇编了代码:

>>> import dis
>>> def test_0():
...     values = range(100)
...     return values[99]
...
>>> def test_1():
...     values = range(100)
...     return values[-1]
...
>>> dis.dis(test_0)
2           
    0 LOAD_GLOBAL              0 (range)
    3 LOAD_CONST               1 (100)
    6 CALL_FUNCTION            1
    9 STORE_FAST               0 (values)

3          
    12 LOAD_FAST                0 (values)
    15 LOAD_CONST               2 (99)
    18 BINARY_SUBSCR
    19 RETURN_VALUE
>>> dis.dis(test_1)
2           
    0 LOAD_GLOBAL              0 (range)
    3 LOAD_CONST               1 (100)
    6 CALL_FUNCTION            1
    9 STORE_FAST               0 (values)

3          
    12 LOAD_FAST                0 (values)
    15 LOAD_CONST               2 (-1)
    18 BINARY_SUBSCR
    19 RETURN_VALUE
>>>
Run Code Online (Sandbox Code Playgroud)

嗯,看起来在指令级别,它几乎是相同的,你需要进入 CPython 实现,才能确切地了解处理负索引时发生了什么,但我认为大多数其他答案已经暗示了这一点。

$ python --version
Python 2.6.1 
Run Code Online (Sandbox Code Playgroud)

出于好奇,我更深入地挖掘并发现了这一点:

在 python 2.7.1 上,但在大多数 python 2.* 上应该是相同的

./Python/ceval.c:

case BINARY_SUBSCR:
    w = POP();
    v = TOP();
    if (PyList_CheckExact(v) && PyInt_CheckExact(w)) {
        /* INLINE: list[int] */
        Py_ssize_t i = PyInt_AsSsize_t(w);
        if (i < 0)
            i += PyList_GET_SIZE(v);
        if (i >= 0 && i < PyList_GET_SIZE(v)) {
            x = PyList_GET_ITEM(v, i);
            Py_INCREF(x);
        }
        else
            goto slow_get;
    }
    else
      slow_get:
        x = PyObject_GetItem(v, w);
    Py_DECREF(v);
    Py_DECREF(w);
    SET_TOP(x);
    if (x != NULL) continue;
    break;
Run Code Online (Sandbox Code Playgroud)

请注意,if (i < 0) i += PyList_GET_SIZE(v);基本上是的,constant 在处理负指数时会产生轻微的开销。

如果您好奇, ./Include/listobject.h: #define PyList_GET_ITEM(op, i) (((PyListObject *)(op))->ob_item[i])那么它基本上是一个查找;)

虽然差异很小,如果你的目标是声明你想要最后一个值,那么这values[-1]更Pythonic /更清晰地表达了这个意图,values[99]简单地意味着如果程序员不知道它有100个值,则获取第99个值他不知道它的最后一个值。