Slicing applies to all programming languages.
It is a way take a piece of a list or array and show it.
Python just has a hyper intuitive way of using slices.
Take a look at a Java Slice
import java.util.Arrays;
import java.util.List;
public class SliceExample {
public static void main(String[] args) {
// Create a list of numbers
List<Integer> numbersList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Convert the list to an array
Integer[] numbersArray = numbersList.toArray(new Integer[0]);
// Get a slice of the array
Integer[] slicedArray = Arrays.copyOfRange(numbersArray, 2, 6);
}
}
Looks a bit complex right? In reality it isn’t all that complex, that is just Java being Java.
Now take a look at a Python slice:
numbersList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
numbersList[2:6]
All the same, but much easier right?
Table of Contents
Getting Index
The square brackets “[]” are a shorthand way to get the individual elements of the list.
Remember, though we are using integers to represent elements in an array, we can use any datatype or class inside an array.
The index number starts at 0 and represents the first element.
Take a look:
numbersList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
numbersList[0]
# Outputs 1
If we were to take the length of an array, the last index would be that length minus 1.
So for example…
numbersList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
numbersList[9]
#Outputs final index
len(numbersList)
#Outputs 10
If we go beyond the length of the numbersList, we get an OutOfBounds error.
numbersList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
numbersList[10]
# Produces OutOfBounds error and crashes
This way of measuring the length of an array with the index starting at 0 applies to all languages.
In Java, Python, C++, Javascript and many more, these index rules are the standard.
It is a great idea to use indexing to the point this becomes second nature.
Negative indices
Negative slices are more of a Python thing. They are extremely useful and I would not be surprised if one day they were added to other mainstream languages.
The basic idea is to use a negative number to represent an approach to the array from the end.
Take a look:
numbersList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
numbersList[-1]
# Outputs 10
numbersList[-2]
# Outputs 9
numbersList[-3]
# Outputs 8
We have -1 as the index being the first. This is because zero is taken up as the first element.
We are choosing elements in reverse this way.
If we go beyond the length again in the negative as well, we get an out of bounds:
numbersList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
numbersList[-11]
# Produces OutOfBounds error
Notice the length is one more of the actual length.
Getting Slice
The slice uses a similar approach of getting an item by index.
This does apply to other languages but appears much more complex outside of Python. If you can understand the concept of slicing in Python it will be much easier to do so in other languages.
Following certain rules, a slice will return another list with the elements asked for within it.
Take a look
[Start:end:Step]
In a slice we have a start, end and step.
Lets take a look at an example:
integer_array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
# Create an integer array with 20 elements
sliced_array = integer_array[3:15:2]
# Outputs new list [3, 5, 7, 9, 11, 13]
We created an array with 20 integers then sliced it into a smaller list. The smaller list is filtered according to the slicing rules.
There are two thing to note in this example. Notice step. The step in the above question is 2.
This means we skip 1 element as we go along the bigger array.
This smaller element then has 3, 5, 7 and skips one number between each.
That is a step.
In addition in that code example index 15 is not included.
In all programming languages whenever we are looping through elements it is standard practice to not include the final index.
Lets look at another example:
integer_array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
# Create an integer array with 20 elements
sliced_array = integer_array[1:18:3]
# Outputs new list [1, 4, 7, 10, 13, 16]
Here we have a step of 3 and we do not include 18. In reality this is 1–17 stepping 3 times before placing an element into the list.
[::]
When slicing we can also exclude numbers and Python will default to some other value.
Take a look:
integer_array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
# Create an integer array with 20 elements
sliced_array = integer_array[1:18]
# Outputs new list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
We decided to not include a step in this version.
By default we get a step of 1. Notice Python did not include 18.
Further, start and end also have default values.
Here is an example:
integer_array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
# Create an integer array with 20 elements
sliced_array = integer_array[:10]
# Outputs new list [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
The start portion of the slice has a default of zero. We did not include it here.
Remember the separating factor between start:end:step is the colon “:”.
Now lets see what end defaults to:
integer_array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
# Create an integer array with 20 elements
sliced_array = integer_array[5:]
# Outputs new list [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
Here the end defaults to the final index. From 5–19 all elements are called.
What if we wanted to default all start:end:step?
Take a look:
integer_array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
# Create an integer array with 20 elements
sliced_array = integer_array[::]
# Outputs new list [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
In this case we are defaulting all parts. Steps is 1, start is 0 and end is the final index 19.
We have to use the double colon because we do not have any other way to show to default the three parts.
Also if we go out of bounds in a slice we just go to the limit of the list.
integer_array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
# Create an integer array with 20 elements
sliced_array = integer_array[1:99]
# Outputs new list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
Negative slices
So long as the start element of the list is to the left of the end element of the list is to the right, we can use negative slices.
Take a look:
integer_array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
# Create an integer array with 20 elements
sliced_array = integer_array[-10:-3]
# Outputs new list [10, 11, 12, 13, 14, 15, 16]
We are referring to elements from the end but we still have to place the start to the left of end.
If we reverse this order we get an empty array…
integer_array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
# Create an integer array with 20 elements
sliced_array = integer_array[-3:-10]
# Outputs new list []
Steps themselves also work in the same way with negative integers.
We simply take that slice and skip through it
integer_array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
# Create an integer array with 20 elements
sliced_array = integer_array[-10:-3:2]
# Outputs new list [10, 12, 14, 16]
Notice negative numbers are essentially the same.
Negative stepping, [end:start:-step]
It is also possible to step in a negative direction.
Essentially, reversing the list.
The only problem is, when we step in the negative we also switch start and end.
But, in all the cases above we can also step backward:
integer_array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
# Create an integer array with 20 elements
sliced_array = integer_array[15:2:-2]
# Outputs new list [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3]
The main confusing part about this negative stepping is how we switch start and end.
All rules apply above, it is just this switching that is different.
We can even use the default values of a slice.
Take a look:
integer_array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
# Create an integer array with 20 elements
sliced_array = integer_array[::-1]
# Outputs new list [19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
That is how we reverse a list…
Slice object
Interestingly, there is an object which represents these slicing actions.
It is known as a slice object in Python.
Though this object is specific to Python, it can be used to take a portion of any list or object, depending on how that class is implemented.
Take a look:
integer_array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
# Create an integer array with 20 elements
my_slice = slice(3, -1, 2)
sliced_array = integer_array[my_slice]
# Outputs new list [3, 5, 7, 9, 11, 13, 15, 17]
The built-in object slice is called with the keyword slice. It initializes the exact same thing as the [start:end:step] and [end:start:-step]. But, it is created with commas and saved in a variable.
This is useful when you want to re-use a slice for different lists sharing the same slices.
Slice dunder
If we want to make slicing compatible with our custom classes we have to use the double underscore to allow slices on it.
Take a look at this implementation of a weather forecast:
from datetime import datetime
class WeatherForecast:
def __init__(self, date, temperatures):
self.date = date
self.temperatures = temperatures
def display_forecast(self):
print(f"Weather forecast for {self.date}:")
for hour, temp in enumerate(self.temperatures, start=1):
print(f"Hour {hour}: {temp}°F")
# Creating a date object for November 11, 2023
forecast_date = datetime(2023, 11, 11)
# List of temperatures per hour
hourly_temperatures = [25, 26, 24, 23, 22, 21, 20, 19, 18, 18, 17, 17, 16, 16, 15, 15, 14, 14, 13, 13, 12, 12, 11, 11]
# Creating a WeatherForecast object
my_forecast = WeatherForecast(forecast_date, hourly_temperatures)
# Displaying the weather forecast
my_forecast.display_forecast()
'''Output
Weather forecast for 2023-11-11 00:00:00:
Hour 1: 25°F
Hour 2: 26°F
Hour 3: 24°F
Hour 4: 23°F
etc.
'''
In this example we have 24 hourly temperature reports in Fahrenheit.
If we wanted to take a slice of these hourly temperatures we could do it like this:
# Creating a date object for November 11, 2023
forecast_date = datetime(2023, 11, 11)
# List of temperatures per hour
hourly_temperatures = [25, 26, 24, 23, 22, 21, 20, 19, 18, 18, 17, 17, 16, 16, 15, 15, 14, 14, 13, 13, 12, 12, 11, 11]
# Creating a WeatherForecast object
my_forecast = WeatherForecast(forecast_date, hourly_temperatures)
my_forecast[1:13]
# Error, no implementation
We can take a slice of the forecast. But we get an error stating there is no implementation.
So, let’s implement a slice…
from datetime import datetime
class WeatherForecast:
def __init__(self, date, temperatures):
self.date = date
self.temperatures = temperatures
def __getitem__(self, key):
if isinstance(key, slice):
# If a slice is provided, return a new WeatherForecast object with sliced temperatures
return WeatherForecast(self.date, self.temperatures[key])
else:
# If a single index is provided, return the temperature at that index
return self.temperatures[key]
def display_forecast(self):
print(f"Weather forecast for {self.date}:")
for hour, temp in enumerate(self.temperatures, start=1):
print(f"Hour {hour}: {temp}°C")
Take a look at that new __getitem__ function. It is known as a magic function.
I write all about the power of magic functions, if you want to take a read.
But basically here, we are organizing how to get indices inside a custom class.
Inside __getitem()__ we check for a slice and get that portion, returning a WeatherReport.
With the above we will get a returned weather report with the sliced temperatures:
# Example usage
if __name__ == "__main__":
# Creating a date object for November 11, 2023
forecast_date = datetime(2023, 11, 11)
# List of temperatures per hour
hourly_temperatures = [25, 26, 24, 23, 22, 21, 20, 19, 18, 18, 17, 17, 16, 16, 15, 15, 14, 14, 13, 13, 12, 12, 11, 11]
# Creating a WeatherForecast object
my_forecast = WeatherForecast(forecast_date, hourly_temperatures)
# Using the __getitem__ method for slicing and accessing individual elements
sliced_forecast = my_forecast[3:10]
single_temperature = my_forecast[5]
# Displaying the sliced forecast and a single temperature
print("Sliced Forecast:")
sliced_forecast.display_forecast()
print(f"Temperature at Hour 5: {single_temperature}°C")
'''Output
Sliced Forecast:
Weather forecast for 2023-11-11 00:00:00:
Hour 1: 23°C
Hour 2: 22°C
Hour 3: 21°C
Hour 4: 20°C
Hour 5: 19°C
Hour 6: 18°C
Hour 7: 18°C
Temperature at Hour 5: 21°C
'''
I recommend asking an LLM to create get_item dunder methods whenever you need them and learn that way.
It is best by examples.
Anywho, I hope you learned something…
CTA: Check out my other articles and follow me on medium.
Happy coding!
Resources
Copy of Chat-GPT logs: https://chat.openai.com/share/51650905-6568-4ab4-8245-38572151ac7c
The power of magic functions: https://jessenerio.com/how-the-awesome-might-of-magic-functions-works-and-is-so-simple/