14

关于内置 python 列表对象的快速问题。假设您有一个编号为 0 - 99 的列表。您正在编写一个程序,该程序获取列表中的最后一项并将其用于其他目的。使用 list[-1] 是否比使用 list[99] 更有效?换句话说,无论哪种情况,python 都会遍历整个列表吗?

谢谢你的帮助。

4

6 回答 6

21

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

于 2012-07-09T17:37:10.527 回答
7

为什么不测试看看?

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)

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

于 2012-07-09T17:43:22.123 回答
4

你可以定时:

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

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

这会引发异常:

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

换句话说,无论哪种情况,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
>>>

好吧,它看起来在指令级别,几乎相同,您需要进入 CPython 实现,以确切了解在处理负索引时发生了什么,但我认为大多数其他答案已经暗示了它.

$ python --version
Python 2.6.1 

出于好奇,我挖得更深,发现了这个:

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

请注意,基本上是的 ,在处理负索引时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 个值他不知道它的最后一个值。

于 2012-07-09T17:50:16.223 回答
3

在任何一种情况下它都不会迭代。list[-1]本质上与 相同list[len(list) - 1]。列表由数组支持,因此查找是恒定时间。

于 2012-07-09T17:36:51.283 回答
2

python中的列表索引总是O(1)。

有关时间复杂度的更多详细信息,请点击此链接

于 2012-10-04T11:19:38.253 回答
1

一个简单的timeit测试结果几乎相等的时间,负指数稍慢

lis=list(xrange(10000000))
def f1():
    a,b,c,d,e=lis[-1],lis[-2],lis[-3],lis[-4],lis[-5]    

def f2():
    a,b,c,d,e=lis[0],lis[1],lis[2],lis[3],lis[4]

if __name__=="__main__":
    from timeit import Timer
    t = Timer("f1()", "from __main__ import f1")
    print t.timeit()
    t = Timer("f2()", "from __main__ import f2")
    print t.timeit()

输出:

0.878027235305
0.845932094722
于 2012-07-09T18:03:38.813 回答