关于内置 python 列表对象的快速问题。假设您有一个编号为 0 - 99 的列表。您正在编写一个程序,该程序获取列表中的最后一项并将其用于其他目的。使用 list[-1] 是否比使用 list[99] 更有效?换句话说,无论哪种情况,python 都会遍历整个列表吗?
谢谢你的帮助。
关于内置 python 列表对象的快速问题。假设您有一个编号为 0 - 99 的列表。您正在编写一个程序,该程序获取列表中的最后一项并将其用于其他目的。使用 list[-1] 是否比使用 list[99] 更有效?换句话说,无论哪种情况,python 都会遍历整个列表吗?
谢谢你的帮助。
Python 不会遍历列表来查找特定索引。列表是连续内存中的数组(指向元素的指针),因此定位所需元素始终是简单的乘法和加法。如果有的话,list[-1]
会稍微慢一些,因为 Python 需要将负索引添加到长度才能获得真正的索引。(我怀疑它明显慢了一点,因为无论如何都是用 C 语言完成的。)
为什么不测试看看?
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)
当然,正如您通过运行几次可以看到的那样,由于其他答案中指出的原因,确实没有(明显的)差异。
你可以定时:
>>> 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 个值他不知道它的最后一个值。
在任何一种情况下它都不会迭代。list[-1]
本质上与 相同list[len(list) - 1]
。列表由数组支持,因此查找是恒定时间。
python中的列表索引总是O(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