Slicing in Python: cutting a list or a string

Slicing cuts a portion out of a Python sequence with square brackets and a colon, and hands back a brand new list or string.
6 min read
Believemy logo

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:

PYTHON
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:

Slice bounds sit between the itemsThe six letters of the word Python carry their positive positions 0 to 5 above and their negative positions -6 to -1 below. The bounds 1 and 4 of the slice sit in the gaps between the letters, never on them: the slice keeps positions 1, 2 and 3, and leaves position 4 outside."Python"[1:4]start 1stop 4positive indexPython012345-6-5-4-3-2-1negative indexresult: "yth"the stop bound sits before rank 4, which stays outside

The calculation below checks it out:

PYTHON
months[:2] + months[2:] == months
# True

Both 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:

WritingWhat 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
Slice the list and watch what comes out
lettres[1:6:1]
a
0
-8
b
1
-7
c
2
-6
d
3
-5
e
4
-4
f
5
-3
g
6
-2
h
7
-1
1
6
1
Result
['b', 'c', 'd', 'e', 'f']

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:

PYTHON
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.

Warning

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.

PYTHON
results = []
first_ones = results[:3]

# [] rather than an error

months[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.

Warning

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:

PYTHON
months[1:3] = ["spring"]

# the list gets one item shorter

The 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

Question

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".

Question

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.

Question

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.

Related terms

Discover our python glossary

Browse the terms and definitions most commonly used in development with Python.

Share this article

Want to help us? Share this article on your networks or even better: on your site, in an article or in your newsletter.