## Negative Sequence Indices in Python

Supply a negative index when accessing a sequence and Python counts back from the end. So, for example, `my_list[-2]`

is the penultimate element of `my_list`

, which is much better than `my_list[len(my_list)-2]`

or even `*(++my_list.rbegin())`

.

That final example uses one of C++’s reverse iterators. It gets the penultimate element of a collection by advancing an iterator from the reversed beginning of that collection. If you’re addicted to negative indices you **can** use them with C++ arrays, sort of.

#include <iostream> int main() { char const * domain = "wordaligned.org"; char const * end = domain + strlen(domain); std::cout << end[-3] << end[-2] << end[-1] << '\n'; return 0; }

Compiling and running this program outputs the string “org”.

Going back to Python, the valid indices into a sequence of length `L`

are `-L`

, `-L+1`

, … , `0`

, `1`

, … `L-1`

. Whenever you write code to calculate an index used for accessing a sequence, and especially if you’re catching any resulting `IndexError`

s, it’s worth checking if the result of the calculation can be negative, and if — in this case — you really do want the from-the-back indexing behaviour.

The power of negative indices increases with slicing. Take a slice of a sequence by supplying begin and end indices.

>>> domain = 'wordaligned.org' >>> domain[4:9] 'align' >>> domain[4:-4] 'aligned' >>> digits = list(range(10)) >>> digits [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> digits[3:4] [3] >>> digits[1:-1] [1, 2, 3, 4, 5, 6, 7, 8]

Omitting an index defaults it to the end of the sequence. Omit both indices and both ends of the sequence are defaulted, giving a sliced copy.

>>> domain[-3:] 'org' >>> domain[:4] 'word' >>> digits[:] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

I prefer the `list(digits)`

form for copying `digits`

but you should certainly be familiar with the `digits[:]`

version.

You can supply any indices as slice limits, even ones which wouldn’t be valid for item access. Imagine laying your sequence out on an indexed chopping board, slicing it at the specified points, then taking whatever lies between these points.

>>> digits[-1000000] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: list index out of range >>> digits[1000000] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: list index out of range >>> digits[-1000000:1000000] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Sequence slicing also takes a step parameter.

>>> digits[::2] [0, 2, 4, 6, 8] >>> digits[1::2] [1, 3, 5, 7, 9]

This parameter too can be negative. The sign of the step affects which limiting values the `begin`

and `end`

slice parameters default to. It’s subtle behaviour, but you soon get used to it.

>>> digits[0:10:-2] [] >>> digits[::-2] [9, 7, 5, 3, 1] >>> digits[-2::-2] [8, 6, 4, 2, 0]

How do you reverse a string? Slice it back to front!

>>> domain[::-1] 'gro.dengiladrow'

To recap: the default slice limits are the start and end of the sequence, `0`

and `-1`

, or `-1`

and `0`

if the step is negative. The default step is `1`

whichever way round the limits are. When slicing, `s[i:j:k]`

, `i`

and `j`

may take any integer value, and `k`

can take any integer value **except** `0`

.

The zero value creates another interesting edge case. Here’s a function to return the last `n`

items of a sequence.

>>> def tail(xs, n) ... return xs[-n:] ...

It fails when `n`

is `0`

.

>>> tail(digits, 3) [7, 8, 9] >>> tail(digits, 2) [8, 9] >>> tail(digits, 1) [9] >>> tail(digits, 0) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

By the way, we’ve already seen slicing working well with lists and strings. It also works nicely with range objects.

>>> r = range(10) >>> r[::2] range(0, 10, 2) >>> r[1::2] range(1, 10, 2)