In Python sequences such as lists, tuples and strings, individual elements can be accessed by indexing. Typically each element have an index  relative to other elements in the list, where the indices starts at 0(the first element) and ends at the length of the sequence minus one.

mylist = ['Python', 'Java', 'C++', 'Ruby']

#access individual elements by their index
print(mylist[0])
print(mylist[1])
print(mylist[2])
print(mylist[3])

In addition to the positive indexing, Python also supports negative indexing. So literally, any element in a Python sequence has two valid indices i.e the positive one and its negative counterpart.  This allows us to conveniently access elements with whichever index gives us the most natural access to the element we want.  

In  negative indexing, the indices starts from the end of a sequence moving to the front. The last element in the sequence is given the index -1, the second-last element is given the index -2, and so on until we reach the front of the list where the first element in the list has an index  equal to  -n, where n is the length of the sequence.

Access list elements using negative indexing

mylist = ['Python', 'Java', 'C++', 'Ruby']

#access individual elements by their negative index
print(mylist[-1])
print(mylist[-2])
print(mylist[-3])
print(mylist[-4])

As shown in the above example, the last element in the list has an index of -1,  and the indices of the rest elements decreases by one as you move to the front of the list.

Negative indexing on strings

mystring = 'Pynerds'

print(mystring[-1])
print(mystring[-2])
print(mystring[-3])
print(mystring[-4])
print(mystring[-5])
print(mystring[-6])

Just like on positive indexing, if the index is not in the valid index range, an IndexError is raised. This is for example if the negative index is smaller than the smallest valid index which is equivalent to  -len(lst).

mytuple = ('Python', 'Java', 'C++', 'Ruby')

#using an invalid index
mytuple[-10]

In the above example, an IndexError is raised because we used -10 as the index where it is not valid since the tuple has only 4 elements and thus the smallest valid index is -4.

Assignment with negative indexing

When the sequence that we are dealing with is mutable such as a list, we can use negative indexing to perform assignment operations on the list. This happens when we override an element in a certain posltion in the list with a new value. 

mylist = ['Python', 'Java', 'C++']

#assign to a position of the list
mylist[-1] = 'Javascript'
print(mylist)

mylist[-3] = 'HTML'
print(mylist)

The assignment operation is not possible with tuples or strings because they are immutable.

Slicing with negative indexes  

Slicing is an operation that is closely related to indexing in that it allows you to select a subset of data from a sequence data type. In other words indexing allows us to access a single elements in the sequences while slicing  allows us to access multiple elements in a sequence in a single statement.

The syntax of slicing is as follows:

seq[start:stop:step]

start 

The starting index of the slice.

stop 

Determines the end of the slice. The stop index is not included on the the slice!
step  Determines the step size of how many elements to jump over when slicing.

We can use negative slicing to  access the elements from back instead of from front.

seq = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']

print(seq[-2: -7: -1])

In the above example, we specified a slice of seq[-2 : -5: -1] meaning that the returned sequence will have elements starting at the second to last element of the sequence (index -2), up to but not including the seventh to last element of the sequence (index -7), with a step size of -1 (so, going backwards).