How to Slice Sequences in Python. Become an expert on slicing lists and strings in Python. | Towards Data Science

Being able to efficiently slice sequences in Python (such as lists, strings, and tuples) is one of the most crucial skills to have when programming. Luckily, python provides indexing syntax that greatly facilitates the slicing process and makes it much more intuitive.
In this tutorial, we will first review how to index a sequence and then move on to slicing sequences, specifically lists and strings. Furthermore, we will cover some important differences between slicing lists and slicing strings. We will then review slice assignment in lists. And lastly, we will look at what exactly is happening when we use the indexing syntax in Python.
Before we start with slicing, let’s briefly review how to index elements in a sequence (specifically a list).
Remember that we can access individual elements in a list by using their index within square brackets. Let’s look at a list of numbers below:
num_list = [0,5,10,15,20,25,30,35,40]            0,1, 2, 3, 4, 5, 6, 7, 8           -9,-8,-7,-6,-5,-4,-3,-2,-1
The index of an element in a sequence is its location in that sequence. In the example above, we have a list of numbers, num_list, and the numbers below that list represent the indices of the corresponding elements. As we may recall, we can either index our sequence starting from the beginning (from the left) with positive indexing starting with index 0, or starting from the end of the sequence (from the right) with negative indexing, starting with index -1.
In other words, if we want to retrieve the number 10 (or third element) from num_list, we can either use its positive index of 2, or negative index of -7:
num_list[2] #10or num_list[-7]#10
If we want to obtain the last number in our list, which is 40, we can do so using the index 8 or -1:
num_list[8]#40ornum_list[-1]#40
Or we can just use the len() function as follows:
num_list[len(num_list)-1]#40
If we use an index value that’s not present in our list or out of range, we will receive an IndexError:
num_list[12]#IndexError: list index out of rangenum_list[-12]#IndexError: list index out of range
Now that we’ve reviewed how to index a sequence using positive and negative indexing, let’s look at slicing.
towardsdatascience.com
We just saw how indexing can be used to retrieve individual elements from a list. Slicing, on the other hand, allows us to obtain a portion from our sequence, such as of a list or string.
Sometimes to understand slicing it can be useful to imagine that the indices are pointing between the elements, rather than to the elements themselves. Although this is only useful when the step value is positive, meaning when we are slicing from left to right. More on step values later.
num_list = [0,5,10,15,20,25,30,35,40] +---+---+----+----+----+----+----+----+----+ | 0 | 5 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | +---+---+----+----+----+----+----+----+----+ 0   1   2    3    4    5    6    7    8    9-9  -8  -7   -6   -5   -4   -3   -2   -1
The syntax for slicing a sequence is as follows:
variable[start:stop:step]
In order to slice a sequence, we need to use a colon within square brackets. In other words, the colon (:) in subscript notation [square brackets] make slice notation. Even though there are three possible values that we can provide within the brackets (the start value, stop value, and step/stride value), we don’t actually have to provide all three unless we need to, as we will see below.
Let’s look at some examples.
One way we can slice a sequence, such as a list, is by specifying the start and stop values. In other words, if we want all the elements between two specific points in our list, we can use the following format:
variable[start:stop]
variable[start:stop] returns the portion of the variable that starts with position start, and up to but not including position stop.
For example, if we want to obtain all the elements from index 2 up to and including index 6, we can do so as follows:
num_list = [0,5,10,15,20,25,30,35,40]num_list[2:7]#[10,15,20,25,30]
Notice how the start value is inclusive, but the stop value is exclusive. Thus, we start with index 2, which is the number 10, and go all the way to but not including the index 7, which would be the number 30 at index 6. If we imagine the indices as between the elements (as seen above), then that is further illustrated, since the index 7 is before the number 35. Since we did not provide a step value, the default step value is 1. Thus, we start with index 2, then take 1 step to index 3, then 1 step to index 4, and so on. In other words, because the step value is positive, we are increasing the index by 1 (moving to the right) as we are slicing our list.
If we want to start at a specific number and go through the entire list, then we would only need to provide the start value.
variable[start:]
variable[start:] returns the portion of the variable that starts with position start, through the end of the sequence.
For example, if we want to retrieve all the elements from the second index through the entire list, we can use the following code:
num_list = [0,5,10,15,20,25,30,35,40]num_list[2:]#[10,15,20,25,30,35,40]
As we can see, if we only provide an index before the colon, then that will be our start index, and we will obtain the rest of the elements in the list (since the step value is still 1).
If we want to start from the beginning of the list and go up to a specific index, then we would only need to provide the stop value.
variable[:stop]
variable[:stop] returns the portion of the variable that starts at the beginning of the sequence, up to but not including position stop.
For example, if we want to retrieve all the elements from the start of the list up to and including index 7, we can do so as follows:
num_list = [0,5,10,15,20,25,30,35,40]num_list[:8]ornum_list[:-1]#[0,5,10,15,20,25,30,35]
Thus, if no number is provided for the start value, then it assumes that we want to start from index 0. And since we want to retrieve all elements up until index 7, we would use the stop value of 8 since it is exclusive. We can also use -1 as the stop value.
We can also mix and match the positive and negative indices. For example, if we want to retrieve all the elements between index 2 up to and including index 7, we can do so as follows:
num_list = [0,5,10,15,20,25,30,35,40]num_list[2:8]or num_list[2:-1]ornum_list[-7:-1]ornum_list[-7:8]#[10,15,20,25,30,35]
Note that in all instances, the stop value is to the right of the start value, since we are using a positive step value. In other words, the stop value must be in the direction of the step value, in relation to the start value. If the step value is positive, then the stop value must be right of the start value. If the step value is negative, then the stop value must be left of the start value. More on that later.
We can also retrieve the entire list by just using a colon with no start or stop values.
variable[:]
variable[:] returns the entire sequence.
num_list = [0,5,10,15,20,25,30,35,40]num_list[:]or num_list[::]#[0,5,10,15,20,25,30,35,40]
towardsdatascience.com
So far we’ve only specified start and/or stop values, where we start at the start value, and end right before the stop value (since it is exclusive). But what if we don’t want all the elements between those two points? What if we want every other element? That’s where the step value comes in.
Let’s say that we want every other value in our list, starting from index 0. Or perhaps we only want the elements at even indices. We can do so using the step value:
variable[::step]
num_list = [0,5,10,15,20,25,30,35,40]num_list[::2]#[0,10,20,30,40]
Since we did not specify a start or stop value, it assumes that we want to start at the beginning of the sequence and go through the entire list. So it starts at index 0, then goes to index 2 (since the step is 2), then to index 4, and so on.
Previously, we mentioned that the stop value must be in the same direction as the step value relative to the start value. In other words, if the step value is positive, which means we are moving to the right, the stop value must be to the right of the start value. If the step value is negative, then the stop value must be to the left of the start value. Otherwise, an empty list is returned:
num_list = [0,5,10,15,20,25,30,35,40]num_list[8:5]#[]num_list[8:5:-1]#[40,35,30]
As we can see, in both examples, the start value is 8, and the stop value is 5, so the stop value is to the left of the start value. In the first example, the step value is +1. Since the stop value is to the left of the start value, and our step value is positive, an empty list is returned, since we cannot move in the direction of the stop value. However, in the second example, we changed the step value to -1. Thus, we start at index 8, which is 40, move 1 index in the negative or left direction to index 7, which is 35, then to index 6, which is 30. We do not go to index 5 because the stop value is exclusive.
Perhaps the most important practical application of the step value is to reverse a sequence. For example, if we want to retrieve the entire list in reverse order, we can do so by using a -1 for the step value:
num_list[::-1]#[40,35,30,25,20,15,10,5,0]
Since we did not specify a start or stop value, then the entire sequence will be retrieved. However, since our step value is -1, it obtains the elements in reverse order.
What if our stop value is greater than the highest index available in our sequence? Or if our start and/or stop values are out of range? In other words, what happens if we ask for more items than are present?
For example, if we try the following:
num_list = [0,5,10,15,20,25,30,35,40]num_list[2:12]#[10,15,20,25,30,35,40]
As we can see, even if we ask for more items than present in our sequence, it just returns whatever elements are present and does not give us an error. In contrast, if we try to index a single element that is out of range (instead of slicing), then we would get an IndexError as we saw earlier.
num_list[12]#IndexError
Indexing and slicing work the same way for strings as well. Again, we can imagine the indices between the characters if we are using a positive step value as follows:
 word = 'Python' +---+---+---+---+---+---+ | P | y | t | h | o | n | +---+---+---+---+---+---+ 0   1   2   3   4   5   6-6  -5  -4  -3  -2  -1
Thus, to obtain the substring ‘yt’ via slicing, we can do so as follows:
word[1:3]#'yt'
To reverse a string, we can do so by using a step value of -1:
word[::-1]#'nohtyP'
Let’s use what we learned to solve a very commonly asked python coding question. We want to write a function that takes in a string, and returns whether or not that string is a palindrome. A string is a palindrome if the reverse of the string is identical to the original string. For example, ‘civic’ is a palindrome, but ‘radio’ is not, since the reverse of ‘radio’ is ‘oidar’, but the reverse of ‘civic’ is ‘civic’.
We just learned how to reverse a sequence by using a step value of -1. Thus we can easily write a function that accomplishes this as follows:
isPalindrome(word):    return word == word[::-1]
And that’s it! The expression word == word[::-1] is evaluated to either True or False. If the string we pass in is equal to its reverse, then the expression evaluates to True, and True is returned. If the string we pass in does not equal its reverse, then the expression evaluates to False, and False is returned.
isPalindrome('civic')# TrueisPalindrome('radio')# False
If we recall, lists are mutable objects in python. In other words, they are able to be mutated, or changed. Thus, we can use slice assignment operation to mutate or edit a list in place.
num_list = [0,5,10,15,20,25,30,35,40]num_list[2:5] = [1,2,3]num_list#[0,5,1,2,3,25,30,35,40]num_list[2:5] = [1,2,3,4,5,6]num_list#[0,5,1,2,3,4,5,6,25,30,35,40]
Notice how we can replace a slice of our list with more or less elements.
We can also delete a part or slice of a list using the del keyword:
num_list = [0,5,10,15,20,25,30,35,40]del num_list[2:5]num_list#[0,5,25,30,35,40]
Note: Strings and tuples are not mutable. Thus we can not edit or mutate them like we can with lists.
Slicing a list will return a copy of that list and not a reference to the original list.
We can see this here: if we assign our list slice to another list, since the list slice returns a copy and not a reference to the original list, we can modify the new list (since lists are mutable) without affecting the original list:
num_list = [0,5,10,15,20,25,30,35,40]# assign a slice of num_list to new_listnew_list = num_list[2:5]new_list#[10,15,20]# replace the third element of new_list with 3new_list[2] = 3# new_list changesnew_list#[10,15,3]# num_list remains the samenum_list#[0,5,10,15,20,25,30,35,40]
In contrast, when we slice a string, a reference to the original string object is returned, and not a copy. And remember, strings are not mutable in Python.
We can use Python’s identity operator (is) and the equality operator (==) to confirm that slicing a list returns a copy or a different object than the original list, but slicing a string returns a reference to the original string object:
Lists:num_list = [0,5,10,15,20,25,30,35,40]num_list == num_list[:]#Truenum_list is num_list[:]#FalseStrings:word = 'Python'word == word[:]#Trueword is word[:]#True
The equality operator (==) checks if the values are equal. The identity operator (is) checks if the two variables point to the same object in memory.
levelup.gitconnected.com
When we use the indexing syntax in python, which includes the use of a colon within square brackets, the built-in slice function is actually being used to create a slice object.
slice(stop)
slice(start, stop[,step])
The slice function can be used in two different ways to create a slice object (similar to the range function which creates a range object). If we pass one argument to the slice function, then that will be the stop value. If we pass in three arguments to the slice function, then they will take on the start, stop, and step values. In other words, the start and step parameters will default to None.
Here are some examples showing the slice objects used when using indexing syntax to slice a list:
num_list[:8] is equivalent to num_list[slice(8)]num_list[2:8] is equivalent to num_list[slice(2,8,None)]num_list[2:] is equivalent to num_list[slice(2,None,None)]
Using the slice function to create a slice object can be useful if we want to save a specific slice object and use it multiple times. We can do so by first instantiating a slice object and assigning it to a variable, and then using that variable within square brackets.
evens = slice(None,None,2)num_list[evens]#[0,10,20,30,40]odds = slice(1,None,2)num_list[odds]#[5,15,25,35]
towardsdatascience.com
In this tutorial, we first reviewed that indexing a sequence means to extract a single element using by using its positive or negative index within square brackets. We then compared indexing a sequence to slicing a sequence, which can retrieve a portion of a sequence. We learned how to slice a sequence with square brackets and a colon, including the different ways to specify which portion we want to retrieve. And since lists are mutable in Python, we saw how we can use slice assignment to change portions of our lists. We then saw the differences between slicing a list and slicing a string, in that slicing a list returns a copy of that list, but slicing a string returns a reference to the original string object. Lastly, we saw how the slice object is actually being used when using slicing notation in Python.