you can timeit:
>>> 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
>>>
theres really no difference, though if you truly want the last item values[-1]
seems be the easiest/safest approach being that it will always grab the last item regardless of the length of the list, as long as its not an empty list,
that would throw an exception:
>>> [][-1]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
>>>
In other words, does python iterate through the whole list in either case?
In neither case would python iterate through the list.
I was actually curious to see if python did anything differently, so I disassemble the code:
>>> 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
>>>
and well it looks like at the instruction level, its pretty much identical, you would need to go into the CPython implementation, to see exactly whats going on when dealing with negative indices, but I think most of the other answers have already hinted at it.
$ python --version
Python 2.6.1
out of curiosity I dugged even deeper and found this:
on python 2.7.1, but it should be the same on most 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;
note the if (i < 0) i += PyList_GET_SIZE(v);
so basically yes theres a slight constant
overhead when dealing with negative indices.
and in case your curious
./Include/listobject.h:
#define PyList_GET_ITEM(op, i) (((PyListObject *)(op))->ob_item[i])
so its basically a look up ;)
though again the difference is tiny and if your goal is to state you want the last value then this values[-1]
is much more pythonic/clearer of making this intent, values[99]
simply means get the 99th value value if the programmer doesn't know it has 100 values then he doesn't know its the last value.