Definition
It often happens that only part of a list or a string is needed: the first three results of a search, everything but the last item, every other name in a contacts file. Writing a loop to pull those items out one by one works, but it weighs down the code. Slicing answers that need directly.
It cuts a portion out of a sequence and hands back a new object, leaving the original untouched. The writing fits in two numbers separated by a colon, inside square brackets: sequence[start:stop]. Here is what that looks like on a list of months:
months = ["january", "february", "march", "april", "may"]
first_quarter = months[0:3]
# ['january', 'february', 'march']This writing applies to everything walked through by position: a list, a tuple, a string, a range object. A dictionary accepts none of it: it has no position, only keys.
The stop bound stays outside
The first position is included, the second one excluded. The asymmetry is unsettling at first, but it earns its keep fast: the size of the slice is exactly stop - start, and two slices meeting at the same position rebuild the whole sequence, with no duplicate and no gap. Here is that idea on the word Python, cut between positions 1 and 4:
The calculation below checks it out:
months[:2] + months[2:] == months
# TrueBoth bounds are optional too. Leaving out the first one starts from the beginning, leaving out the second one runs to the end, leaving out both hands back the full sequence. A negative position counts from the end, which saves working out a length with len just to reach the last few items. Here are the writings that come up most often:
| Writing | What it hands back |
|---|---|
months[2:] | From the third item to the end |
months[:2] | The first two items |
months[-2:] | The last two items |
months[:-1] | Everything but the last one |
months[::2] | Every other item |
months[::-1] | The sequence in reverse order |
The step is positive, reading runs left to right and stops before the end bound, which is excluded.
Negative positions count from the end. Slicing never raises an error, even out of bounds: that is what sets it apart from direct access by position.
The step, that third number
A second colon introduces the step: sequence[start:stop:step]. It defaults to 1 and says how many positions to move forward between two kept items. Here is what that changes on a list of ten numbers:
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
numbers[::3] # [0, 3, 6, 9]
numbers[1:8:2] # [1, 3, 5, 7]A negative step walks the sequence backwards. That is where the most recognisable writing in the language comes from, sequence[::-1], which hands back the reversed sequence, on a string just as on a list.
With a negative step, the bounds are read backwards too: start has to be greater than stop, not the other way round. numbers[2:8:-1] does not hand back positions 2 to 8 reversed, but an empty list, since there is no way to walk back from position 2 towards position 8.
It never raises a position error
Asking a sequence for a position that does not exist normally stops the program: on a list of five months, months[10] raises an IndexError. A slice behaves differently though.
results = []
first_ones = results[:3]
# [] rather than an errormonths[10:20] on five items quietly hands back an empty list. Slicing trims the bounds down to the real size of the sequence instead of complaining when they reach too far.
That forgiveness is a blessing for reading the first three results of a search or the opening lines of a file. It becomes a trap when it hides an empty data set: an empty slice reports nothing. If the rest of the program assumes at least one item, that assumption is worth checking explicitly rather than left to slicing to warn about.
A shallow copy, and slice assignment
On a list, slicing always hands back a new object: copy = original[:] is the short writing of a full copy, with no loop to write.
That copy stays shallow: the items are not duplicated. If the list holds other lists, both versions share their contents, and changing a mutable item on one side shows up on the other.
On a string the question does not arise: the text is immutable, and cutting it always produces an independent value.
On the left of the equals sign, a slice changes role: it no longer reads, it replaces. The list is changed in place, and the number of items inserted does not have to match the number taken out:
months[1:3] = ["spring"]
# the list gets one item shorterThe same logic holds for del: del months[1:3] removes the slice and closes the list behind it. Both writings only work on changeable sequences. The same attempt on a tuple or on a string raises a TypeError.
Frequently asked questions
Why does my slice hold one item fewer than expected?
Because the stop bound is excluded. Getting positions 2 to 5 included means writing [2:6] and not [2:5]. The habit that settles the mistake for good is reading the second number as "we stop before this one", never as "up to this one".
Can decimal numbers be used inside a slice?
No, the bounds and the step have to be whole numbers of type int. A decimal number raises a TypeError even when it lands on a round value, which is surprising after a division: division always hands back a float, and integer division with // is needed to get a usable position.
How can a dictionary be sliced?
There is no slicing on a dictionary, since its keys have no position to point at. Keeping only part of it means filtering the wanted keys inside a list comprehension applied to the pairs, which rebuilds a smaller dictionary.