Python for Programmers: List Slicing
Welcome to the List Slicing lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
We often want just a part of a list, like "only the first 4 elements", or "only elements 7 through 11", or "only the last 6 elements". Python's list slicing operator lets us do all of those and more.
some_list[2:]gives us a list containing every element whose index is greater than or equal to2.>
visitors = ["Amir", "Betty", "Cindy", "Dalili"]visitors[2:]Result:
['Cindy', 'Dalili']
If we put a number after the
:, we get every element up to, but not including, that index.:2gives us indexes 0 and 1.>
visitors = ["Amir", "Betty", "Cindy", "Dalili"]visitors[:2]Result:
['Amir', 'Betty']
We can also specify the start and end index together.
some_list[1:3]starts atsome_list[1]and goes tosome_list[3], but doesn't includesome_list[3]. That is, it only includes indexes 1 and 2.>
visitors = ["Amir", "Betty", "Cindy", "Dalili"]visitors[1:3]Result:
['Betty', 'Cindy']
Slicing is useful when combined with negative indexing.
some_list[:-2]returns all list elements except for the last 2. We can think of it as "all elements up to, but not including, the last 2 elements".>
winning_ids = [10, 5, 3, 2, 1]winning_ids[:-2]Result:
[10, 5, 3]
If we slice outside of a list's bounds, we get an empty list.
>
some_list = [1, 2, 3]some_list[5:]Result:
[]
If the sliced range only partially overlaps with the actual list indexes, we get the part of the list that falls within the range.
>
some_list = [1, 2, 3]some_list[2:20]Result:
[3]
This slicing behavior is a bit unusual for Python. In most other situations, Python is conservative about list indexes. For example, it raises an exception when we access indexes that don't exist. That makes sense for simple list indexing like
some_list[some_index]: if we accidentally index past the end of the list, that's probably a bug that we want to know about.With list slicing, we'd have to do some tedious math to make sure that every index in our slice is actually within the bounds of the list. That would defeat the purpose of Python's terse slice syntax. Python simply skips the bounds check when we slice, which keeps our code simple.
We often use slices to split lists into two parts. For example, imagine that we're writing a "worker process" that handles tasks stored in a list. A worker can handle up to 3 tasks at a time, so we want to remove up to 3 from our list at once. We need the list of those tasks, so we can assign them to the worker. But we also need to remove those tasks from the list of remaining tasks, since they're now assigned.
>
remaining_task_ids = [10, 20, 30, 40, 50, 60]# Take a bundle of tasks to be assigned to this worker.task_capacity = 3tasks_to_assign = remaining_task_ids[:task_capacity]# Remove these tasks from the remaining_task_id list.remaining_task_ids = remaining_task_ids[task_capacity:]- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
tasks_to_assignResult:
[10, 20, 30]
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
remaining_task_idsResult:
[40, 50, 60]
That example uses a common pattern for slicing a list into two parts at a certain index:
some_list[:index]gives us everything beforesome_list[index].some_list[index:]gives us everything starting atsome_list[index].
Together, these two expressions let us quickly partition a list without worrying about edge cases.
>
numbers = [0, 1, 2, 3, 4]chunks = []chunk_size = 2while len(numbers) > 0:chunks.append(numbers[:chunk_size])numbers = numbers[chunk_size:]chunksResult:
[[0, 1], [2, 3], [4]]
We've seen that the slice syntax can contain a start index (
[2:]), an end index ([:3]), or both ([2:3]). It can also contain neither index! The slicesome_list[:]gives us every element in the list.>
original_list = [1, 2, 3]new_list = original_list[:]new_listResult:
[1, 2, 3]
Although the lists contain the same elements, they're independent lists, stored in different places in memory. Changes to one list won't affect the other.
>
original_list = [1, 2]new_list = original_list[:]new_list.append(3)- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
original_listResult:
[1, 2]
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
new_listResult:
[1, 2, 3]
This is also true if we only slice a part of the list. Slicing always creates a fresh list.
>
original_list = [1, 2, 3]new_list = original_list[:2]new_list.append(4)- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
original_listResult:
[1, 2, 3]
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
new_listResult:
[1, 2, 4]
What if we slice a list that contains mutable elements, like lists or dictionaries? The elements themselves aren't copied. The original list and the new sliced list both reference the same element values, stored at the same places in memory.
In the example below, we copy a list that contains dictionaries. When we modify one of the dictionaries in one copy of the list, our changes are also visible in the other copy.
>
original_list = [{"name": "Amir"}, {"name": "Betty"}]copy = original_list[:]original_list[0]["age"] = 36copy[0]Result:
{'name': 'Amir', 'age': 36}This is a subtle but important point: slicing a list gives us a new list, independent from the original list. But it doesn't copy the individual list elements! In other words, slicing can make a "shallow copy" of the list, but it can't make a "deep copy" of the list and all of its elements.
Slices also work when assigning. In the simplest case,
some_list[:] = another_listreplaces the entire contents ofsome_listwithanother_list.>
some_list = [1, 2, 3]some_list[:] = ["a", "b", "c"]some_listResult:
['a', 'b', 'c']
When we assign to a slice, we replace part of a list with another list.
some_list[1:3] = another_listreplaces all values fromsome_list[1:3]with the values ofanother_listThat is, it replaces indexes 1 and 2.>
some_list = [1, 2, 3, 4, 5]some_list[1:3] = [6, 7]some_listResult:
[1, 6, 7, 4, 5]
When replacing in this way, the replacement doesn't need to be the same size as the section that it's replacing. For example, we can replace a 2-element section of a list with 5 new elements. The resulting list will be 3 elements longer than the original.
>
some_list = ["a", "b", "c", "d", "e"]some_list[1:3] = ["x", "y", "z"]some_listResult:
>
some_list = ["a", "b", "c", "d", "e"]some_list[2:3] = ["x", "y"]some_listResult:
['a', 'b', 'x', 'y', 'd', 'e']
That even works with an empty slice. For example,
some_list[2:2] = another_listinsertsanother_listintosome_liststarting at index 2. The slice2:2means "every index that is at least 2, but is also less than 2."There are no indexes that are at least 2, but also less than 2, so the slice matches no indexes. When we assign to that slice, we're saying "insert this other list's elements, starting at index 2." It only inserts elements; none are removed.
>
some_list = [1, 2, 3, 4, 5]some_list[2:2] = [600, 700]some_listResult:
>
some_list = [1, 2, 3, 4, 5]some_list[4:4] = [800]some_listResult:
[1, 2, 3, 4, 800, 5]
Finally, here's a helpful way to think about slice assignment. We can think of
some_list[start:end] = another_listas happening in two steps.- We remove all the elements found within
some_list[start:end]. - We insert all the elements from
another_listintosome_list, starting atsome_list[start].
- We remove all the elements found within
Here's a code problem:
Write a function,
remove_outliers, that takes two arguments: a sorted list (the_list) and a number (n). It should return the list with the firstnand lastnelements removed. For example,remove_outliers([1, 2, 3, 4, 5, 6], 2)should return[3, 4]. Use Python's slice syntax to remove the elements.(The solution already contains some code to handle the edge case where
n == 0.)def remove_outliers(the_list, n):if n == 0:return the_listelse:return the_list[n:-n]assert remove_outliers([1, 2, 3, 4, 5, 6], 2) == [3, 4]assert remove_outliers([2, 5, 7, 8, 10, 12], 2) == [7, 8]assert remove_outliers([1, 4, 7, 11, 15], 1) == [4, 7, 11]assert remove_outliers([1, 4, 5], 0) == [1, 4, 5]- Goal:
None
- Yours:
None