This article was originally published at What is slicing in Python?
Slicing in Python is a feature that enables accessing parts of sequences like strings, tuples, and lists. You can also use them to modify or delete the items of mutable sequences such as lists. Slices can also be applied on third-party objects like NumPy arrays, as well as Pandas series and data frames.
Slicing enables writing clean, concise, and readable code.
This article shows how to access, modify, and delete items with indices and slices, as well as how to use the built-in class slice().
Indexing
You can access a single item of a Python sequence (such as string, tuple, or list) with the corresponding integer indices. Indices in Python are zero-based. This means the index 0 corresponds to the first (leftmost) item of a sequence, 1 to the second item, and so on.
Let’s create one string, one tuple, and one list to illustrate how to use indices:
>>> str_ = 'Python is awesome!'
>>> str_
'Python is awesome!'
>>> tuple_ = (1, 2, 4, 8, 16, 32, 64, 128)
>>> tuple_
(1, 2, 4, 8, 16, 32, 64, 128)
>>> list_ = [1, 2, 4, 8, 16, 32, 64, 128]
>>> list_
[1, 2, 4, 8, 16, 32, 64, 128]
Indices are provided inside the brackets, that is with the syntax sequence[index]:
>>> str_[0]
'P'
>>> str_[1]
'y'
>>> str_[4]
'o'
>>> tuple_[0]
1
>>> tuple_[1]
2
>>> tuple_[4]
16
>>> list_[0]
1
>>> list_[1]
2
>>> list_[4]
16
As you can see ‘P’ is the first character of the string ‘Python is awesome!’ and it corresponds to the index 0, ‘y’ is the second and has index 1 and so on.
It’s very similar to tuples and lists.
Python provides the possibility to use negative integer indices as well. This appears to be very useful in practice, especially when you want to access the last (rightmost) item of a sequence.
The index -1 corresponds to the last item, -2 to the second last, and so on:
>>> str_[-1]
'!'
>>> str_[-2]
'e'
>>> str_[-5]
's'
>>> tuple_[-1]
128
>>> tuple_[-2]
64
>>> tuple_[-5]
8
>>> list_[-1]
128
>>> list_[-2]
64
>>> list_[-5]
8
As you can see, the character ‘!’ is the last item in the string ‘Python is awesome!’ and it corresponds to the index -1. It also has the index 17, which is string_[17] returns ‘!’. The character ‘e’ takes place just in front of it and has the index -2 and 16.
Again, tuples and lists behave similarly.
You can use indices to modify the items of the mutable sequence. In Python, strings and tuples are immutable, but lists are mutable:
>>> list_[-1] = 100
>>> list_
[1, 2, 4, 8, 16, 32, 64, 100]
This example changes the last item of the list to 100. Since this list has eight items, you can also access the last item with the zero-based index 7, that is you can accomplish the same effect with the statement list_[7] = 100.
Slicing
Slicing is similar to indexing but returns a sequence of items instead of a single item. The indices used for slicing are also zero-based.
There are two variants of the slicing syntax: sequence[start:stop] and sequence[start:stop:step]. This article explains both.
When you use the syntax sequence[start:stop], you’ll get the new sequence. It will start with the item that has the index start (inclusive) and end before the item with the index stop. In other words, the statement sequence[start:stop] returns the items sequence[start], sequence[stop-1], and all the items between them:
>>> str_[1:5]
'ytho'
>>> tuple_[2:4]
(4, 8)
>>> tuple_[2:3]
(4,)
>>> tuple_[2:2]
()
As you can see, the returning sequence can contain only one item when start + 1 == stop or it can be an empty sequence when start == stop.
When you omit start, the value 0 is taken, that is the resulting sequence starts at the beginning of the original:
>>> str_[:2]
'Py'
>>> tuple_[:4]
(1, 2, 4, 8)
If you omit to stop, the resulting sequence stops at the end of the original:
>>> str_[2:]
'thon is awesome!'
>>> tuple_[4:]
(16, 32, 64, 128)
Consistently, you can omit both start and stop:
>>> str_[:]
'Python is awesome!'
>>> tuple_[:]
(1, 2, 4, 8, 16, 32, 64, 128)
>>> list_[:]
[1, 2, 4, 8, 16, 32, 64, 100]
That’s how you get a shallow copy of a sequence.
It’s possible to apply negative values of start and stop similarly as with indices:
>>> str_[-8:]
'awesome!'
>>> tuple_[:-1]
(1, 2, 4, 8, 16, 32, 64)
>>> tuple_[-4:-2]
(8, 16)
>>> tuple_[3:5]
(8, 16)
In the case of tuple_, the value -4 — when counting backward from -1 — corresponds to the same item as the value 3 when counting forward from 0. It’s the same for the values 5 and -2. Again, the item that corresponds to the index stop isn’t included in the resulting sequence.
The syntax sequence[start:stop:step] is similar, but with few more details.
You can use a step to specify the step if you want to skip some items:
>>> tuple_[1:5:2]
(2, 8)
In this example, step is 2, so you start with the item that has the index 1 (the item 2), collect every second item, and stop before you reach the item with the index 5 (32). You take the item 2, skip 4, and take 8.
If the step is positive and you omit start, the resulting sequence again starts at the beginning of the original. If you omit to stop, the operation stops at the end of the original. However, if you omit a step, it’s considered as 1 and you get the same behavior as with the syntax sequence[start:stop]:
>>> tuple_[:6:2]
(1, 4, 16)
>>> tuple_[3::2]
(8, 32, 128)
>>> tuple_[1:5:]
(2, 4, 8, 16)
A negative value of step, in combination with a start greater than stop, can be used to collect items backward:
>>> tuple_[4:1:-1]
(16, 8, 4)
>>> tuple_[6:1:-2]
(64, 16, 4)
In the cases with negative step and omitted start, the resulting sequence starts at the end of the original. If you omit to stop, the operation stops at the beginning of the original:
>>> tuple_[:2:-1]
(128, 64, 32, 16, 8)
>>> tuple_[5::-1]
(32, 16, 8, 4, 2, 1)
These rules are conveniently applied to get the shallow copy of a sequence with the items in the reversed order by omitting both, start and stop:
>>> tuple_[::-1]
(128, 64, 32, 16, 8, 4, 2, 1)
You can use slices to modify the items of mutable sequences:
>>> list_[1:6:2]
[2, 8, 32]
>>> list_[1:6:2] = [20, 80, 320]
>>> list_
[1, 20, 4, 80, 16, 320, 64, 100]
In this example, the items with the indices 1, 3, and 5 are accessed and modified.
If you use the syntax sequence[start:stop], it’s possible to replace a part of a sequence with a smaller or larger one:
>>> list_[1:5]
[20, 4, 80, 16]
>>> list_[1:5] = [0, 0]
>>> list_
[1, 0, 0, 320, 64, 100]
In this example, four items are removed ([20, 4, 80, 16]) and two new ones ([0, 0]) are added instead.
Following this logic, you can delete parts of mutable sequences by replacing them with empty sequences like []:
>>> list_[1:3]
[0, 0]
>>> list_[1:3] = []
>>> list_
[1, 320, 64, 100]
It’s also possible to delete parts of sequences using slices in the del statement:
>>> list_[:2]
[1, 320]
>>> del list_[:2]
>>> list_
[64, 100]
So, there are two approaches to delete a part of a list. However, the latter seems more readable and easily understandable because using the del statement makes your intention (to delete something) obvious.
What Are Slices?
The slices are the instances of the Python built-in class slice. You create them with the statement slice(start, stop, step). It’s possible to pass an instance of a slice instead of start:stop:step or start:stop:
>>> s = slice(1, 5, 2)
>>> s
slice(1, 5, 2)
>>> s.start, s.stop, s.step
(1, 5, 2)
>>> tuple_[s]
(2, 8)
Again, you can omit step and get the behavior of start:stop:
>>> s = slice(1, 5)
>>> s.start, s.stop, s.step
(1, 5, None)
>>> tuple_[s]
(2, 4, 8, 16)
If you pass a single argument, slice uses it as stop and behaves as :stop:
>>> s = slice(5)
>>> s.start, s.stop, s.step
(None, 5, None)
>>> tuple_[s]
(1, 2, 4, 8, 16)
You can manipulate slices inside functions and methods:
>>> def get_items_with_slice(sequence, start, stop, step):
... return sequence[slice(start, stop, step)]
...
>>> get_items_with_slice([1, 2, 4, 8, 16, 32, 64, 128], 1, 5, None)
[2, 4, 8, 16]
This function uses its own arguments to create a slice and passes it to get some data from a sequence.
Finally, here’s an example of how you can use slices in combination with the Python special method _getite_() to define a class whose instances can be used with the index notation:
>>> class C:
... def __init__(self, *args):
... self.__data = args
... def __getitem__(self, index_or_slice):
... print('index or slice:', index_or_slice)
... return self.__data[index_or_slice]
...
>>> x = C(1, 2, 4, 8, 16, 32, 128)
>>> x[4]
index or slice: 4
16
>>> x[1:5:2]
index or slice: slice(1, 5, 2)
(2, 8)
If you pass an integer to __getitem__()
, it will behave as an index of self.data. If you pass start:stop:step instead, `getitem__()` actually obtains a slice object as an argument.
You can also use __setitem__()
to modify data and __delitem__()
to delete data, potentially combined with slices.
Conclusions
This article shows how to use slices in Python. They are very powerful and useful when you need to extract items from sequences like strings, tuples, and lists. Also, some third-party objects such as NumPy arrays and Pandas series and data frames.
You can use slices inside functions and methods. If you want to define classes with the possibility to access, modify, or delete data with the instance[index_or_slice] notation, you should implement the special methods __getitem__(), __setitem__(), or __delitem__()
and possibly apply slices.
Thank you for reading!