PythonMastery
reference 5 min read · lesson 9 of 25 in Reference

Python Slicing Cheat Sheet

1 · The lesson

read

A slice takes some of the items from a list, a string, a tuple or another sequence (a collection whose items have positions). It gives you the items as a new sequence of the same type. You write it as seq[start:stop:step]. This page shows each part with examples you can run, and the mistakes people make most often. For an introduction, see Lists and Strings.


1. Start and stop

The item at start is included and the item at stop is not. You can leave out any of the three parts:

Left outIt means
startfrom the beginning (from the end when the step is negative)
stopto the end (to the beginning when the step is negative)
step1: every item
python
xs = [0, 1, 2, 3, 4, 5]
xs[1:4]            # [1, 2, 3]  (index 1, 2 and 3; index 4 is not included)
xs[:3]             # [0, 1, 2]  (the first 3 items)
xs[3:]             # [3, 4, 5]  (from index 3 to the end)
xs[:]              # [0, 1, 2, 3, 4, 5]  (a copy of the whole list)
xs[:2] + xs[2:]    # [0, 1, 2, 3, 4, 5]  (the two parts join back into the whole list)

2. Count from the end

A negative number counts from the end: -1 is the last item, and -2 is the item before the last.

python
xs[-1]       # 5  (an index, not a slice: the last item itself)
xs[-2:]      # [4, 5]  (the last 2 items)
xs[:-2]      # [0, 1, 2, 3]  (every item except the last 2)
xs[1:-1]     # [1, 2, 3, 4]  (every item except the first and the last)
xs[-4:-1]    # [2, 3, 4]  (up to, but not including, the last item)
+ setup added so this can run · re-runs 1 earlier block on this page first
# Lightweight mock for objects whose attributes/methods aren't critical
class _AutoMock:
    def __init__(self, name='mock'): self._name = name
    def __getattr__(self, k): return _AutoMock(self._name + '.' + k)
    def __call__(self, *a, **kw):
        print('-> ' + self._name + '() called')
        return _AutoMock(self._name + '()')
    def __repr__(self): return '<mock ' + self._name + '>'
    def __str__(self): return '<mock ' + self._name + '>'
    def __bool__(self): return True
    def __iter__(self): return iter([])
    def __len__(self): return 0
    def __getitem__(self, k): return _AutoMock(self._name + '[...]')
    def __setitem__(self, k, v): pass
    def __enter__(self): return self
    def __exit__(self, *a): return False
    async def __aenter__(self): return self
    async def __aexit__(self, *a): return False
    def __add__(self, o): return self
    def __radd__(self, o): return self
    def __sub__(self, o): return self
    def __mul__(self, o): return self
    def __rmul__(self, o): return self
    def __truediv__(self, o): return self
    def __eq__(self, o): return isinstance(o, _AutoMock)
    def __hash__(self): return hash(self._name)
    def __lt__(self, o): return True
    def __le__(self, o): return True
    def __gt__(self, o): return False
    def __ge__(self, o): return False
    def __mro_entries__(self, bases): return (object,)

xs = _AutoMock('xs')

# Earlier blocks on this page that this one builds on, re-run quietly first.
_pm_earlier = [
    'xs = [0, 1, 2, 3, 4, 5]\nxs[1:4]            # [1, 2, 3]  (index 1, 2 and 3; index 4 is not included)\nxs[:3]             # [0, 1, 2]  (the first 3 items)\nxs[3:]             # [3, 4, 5]  (from index 3 to the end)\nxs[:]              # [0, 1, 2, 3, 4, 5]  (a copy of the whole list)\nxs[:2] + xs[2:]    # [0, 1, 2, 3, 4, 5]  (the two parts join back into the whole list)',
]
def _pm_run_earlier(_srcs, _g):
    import sys as _s, io as _io, warnings as _w
    for _src in _srcs:
        _o, _e = _s.stdout, _s.stderr
        _s.stdout = _s.stderr = _io.StringIO()
        try:
            with _w.catch_warnings():
                _w.simplefilter('ignore')
                exec(compile(_src, '<earlier block>', 'exec'), _g)
        except (Exception, SystemExit):
            pass
        finally:
            _s.stdout, _s.stderr = _o, _e
    if 'matplotlib.pyplot' in _s.modules:
        _s.modules['matplotlib.pyplot'].close('all')   # their plots belong to their own blocks
_pm_run_earlier(_pm_earlier, globals())
del _pm_earlier, _pm_run_earlier

3. Step

The third number is the step: how far to move each time. A step of 2 takes every second item.

python
xs[::2]      # [0, 2, 4]  (every second item)
xs[1::2]     # [1, 3, 5]  (every second item, starting at index 1)
xs[1:5:2]    # [1, 3]  (start 1, stop 5, step 2)
xs[::3]      # [0, 3]
+ setup added so this can run · re-runs 1 earlier block on this page first
xs = ["alpha", "beta", "gamma"]

# Earlier blocks on this page that this one builds on, re-run quietly first.
_pm_earlier = [
    'xs = [0, 1, 2, 3, 4, 5]\nxs[1:4]            # [1, 2, 3]  (index 1, 2 and 3; index 4 is not included)\nxs[:3]             # [0, 1, 2]  (the first 3 items)\nxs[3:]             # [3, 4, 5]  (from index 3 to the end)\nxs[:]              # [0, 1, 2, 3, 4, 5]  (a copy of the whole list)\nxs[:2] + xs[2:]    # [0, 1, 2, 3, 4, 5]  (the two parts join back into the whole list)',
]
def _pm_run_earlier(_srcs, _g):
    import sys as _s, io as _io, warnings as _w
    for _src in _srcs:
        _o, _e = _s.stdout, _s.stderr
        _s.stdout = _s.stderr = _io.StringIO()
        try:
            with _w.catch_warnings():
                _w.simplefilter('ignore')
                exec(compile(_src, '<earlier block>', 'exec'), _g)
        except (Exception, SystemExit):
            pass
        finally:
            _s.stdout, _s.stderr = _o, _e
    if 'matplotlib.pyplot' in _s.modules:
        _s.modules['matplotlib.pyplot'].close('all')   # their plots belong to their own blocks
_pm_run_earlier(_pm_earlier, globals())
del _pm_earlier, _pm_run_earlier

4. Backwards: a negative step

A negative step goes from right to left. Now start must be the bigger index.

python
xs[::-1]     # [5, 4, 3, 2, 1, 0]  (a reversed copy)
xs[::-2]     # [5, 3, 1]
xs[4:1:-1]   # [4, 3, 2]
xs[3::-1]    # [3, 2, 1, 0]  (from index 3 back to the start)
xs[:2:-1]    # [5, 4, 3]  (from the end back to index 2, not including it)
+ setup added so this can run · re-runs 1 earlier block on this page first
xs = ["alpha", "beta", "gamma"]

# Earlier blocks on this page that this one builds on, re-run quietly first.
_pm_earlier = [
    'xs = [0, 1, 2, 3, 4, 5]\nxs[1:4]            # [1, 2, 3]  (index 1, 2 and 3; index 4 is not included)\nxs[:3]             # [0, 1, 2]  (the first 3 items)\nxs[3:]             # [3, 4, 5]  (from index 3 to the end)\nxs[:]              # [0, 1, 2, 3, 4, 5]  (a copy of the whole list)\nxs[:2] + xs[2:]    # [0, 1, 2, 3, 4, 5]  (the two parts join back into the whole list)',
]
def _pm_run_earlier(_srcs, _g):
    import sys as _s, io as _io, warnings as _w
    for _src in _srcs:
        _o, _e = _s.stdout, _s.stderr
        _s.stdout = _s.stderr = _io.StringIO()
        try:
            with _w.catch_warnings():
                _w.simplefilter('ignore')
                exec(compile(_src, '<earlier block>', 'exec'), _g)
        except (Exception, SystemExit):
            pass
        finally:
            _s.stdout, _s.stderr = _o, _e
    if 'matplotlib.pyplot' in _s.modules:
        _s.modules['matplotlib.pyplot'].close('all')   # their plots belong to their own blocks
_pm_run_earlier(_pm_earlier, globals())
del _pm_earlier, _pm_run_earlier

5. Strings

Slicing works on strings in the same way. A string cannot change, so you build a new string from slices.

python
s = "python"
s[:2]                                       # 'py'
s[-3:]                                      # 'hon'
s[::-1]                                     # 'nohtyp'  (the string reversed)
"level"[::-1] == "level"                    # True  (a palindrome: a word that reads the same backwards)
s[0].upper() + s[1:]                        # 'Python'
"report.csv"[:-4]                           # 'report'  ("report.csv".removesuffix(".csv") is clearer)
[s[i:i + 2] for i in range(0, len(s), 2)]   # ['py', 'th', 'on']  (cuts the string into pieces of 2)

6. Other sequences

A slice gives you the same type you started with. A generator (an object that makes values one at a time, such as (n * n for n in range(9))) has no index, so use itertools.islice() to take part of it.

python
from itertools import islice
(1, 2, 3)[1:]                        # (2, 3)  (a slice of a tuple is a tuple)
range(10)[2:8:2]                     # range(2, 8, 2)  (a range slice is a range, not a list)
b"hello"[1:3]                        # b'el'
squares = (n * n for n in range(9))
list(islice(squares, 3))             # [0, 1, 4]

A dict and a set have no positions, so you cannot slice them. See the itertools cheat sheet for more on islice().

7. Change a list through a slice

You can assign to a slice of a list. The new part can be longer or shorter than the part it replaces. This only works for lists: strings, tuples and ranges cannot change.

python
xs = [0, 1, 2, 3, 4, 5]
xs[1:3] = [7, 8, 9]    # [0, 7, 8, 9, 3, 4, 5]  (replaces 2 items with 3 items; the list grows)
xs[1:4] = []           # [0, 3, 4, 5]  (assigning an empty list removes the items at index 1, 2 and 3)
xs[:0] = [9]           # [9, 0, 3, 4, 5]  (assigning to an empty slice inserts items)
del xs[::2]            # [0, 4]  (del removes every second item)

You can keep a slice in a variable with slice(), which helps when you use the same slice many times:

python
first_three = slice(0, 3)
"Margaret"[first_three]     # 'Mar'
[10, 20, 30, 40][first_three]   # [10, 20, 30]

8. Copies

A slice of a list is a new list, so changing it does not change the original. It is a shallow copy: if the list holds other lists, those inner lists are shared.

python
xs = [0, 1, 2, 3, 4, 5]
ys = xs[:]
ys[0] = 99
xs                       # [0, 1, 2, 3, 4, 5]  (xs did not change)
grid = [[1], [2]]
cp = grid[:]
cp[0].append(9)
grid                     # [[1, 9], [2]]  (the inner list is shared)

NumPy arrays are different. A slice of an array is a view: it shares the data with the original array, so a change through the slice changes the original. Call .copy() when you need a separate array.

python
import numpy as np
arr = np.array([1, 2, 3, 4])
part = arr[1:3]
part[0] = 99
arr.tolist()             # [1, 99, 3, 4]
safe = arr[1:3].copy()

9. Past the end

An index past the end raises an error. A slice past the end stops at the end of the list, and never raises an error.

python
xs = [0, 1, 2, 3, 4, 5]
xs[4:99]     # [4, 5]
xs[10:20]    # []  (no items there: an empty list, no error)
[][:1]       # []  (no error on an empty list; [][0] would raise IndexError)

10. Useful patterns

python
k = 2
xs[k:] + xs[:k]                 # [2, 3, 4, 5, 0, 1]  (moves the first k items to the end)
xs[-k:] + xs[:-k]               # [4, 5, 0, 1, 2, 3]  (moves the last k items to the front)
list(zip(xs, xs[1:]))[:3]       # [(0, 1), (1, 2), (2, 3)]  (pairs each item with the next item)
+ setup added so this can run · re-runs 4 earlier blocks on this page first
xs = ["alpha", "beta", "gamma"]

# Earlier blocks on this page that this one builds on, re-run quietly first.
_pm_earlier = [
    'xs = [0, 1, 2, 3, 4, 5]\nxs[1:4]            # [1, 2, 3]  (index 1, 2 and 3; index 4 is not included)\nxs[:3]             # [0, 1, 2]  (the first 3 items)\nxs[3:]             # [3, 4, 5]  (from index 3 to the end)\nxs[:]              # [0, 1, 2, 3, 4, 5]  (a copy of the whole list)\nxs[:2] + xs[2:]    # [0, 1, 2, 3, 4, 5]  (the two parts join back into the whole list)',
    'xs = [0, 1, 2, 3, 4, 5]\nxs[1:3] = [7, 8, 9]    # [0, 7, 8, 9, 3, 4, 5]  (replaces 2 items with 3 items; the list grows)\nxs[1:4] = []           # [0, 3, 4, 5]  (assigning an empty list removes the items at index 1, 2 and 3)\nxs[:0] = [9]           # [9, 0, 3, 4, 5]  (assigning to an empty slice inserts items)\ndel xs[::2]            # [0, 4]  (del removes every second item)',
    'xs = [0, 1, 2, 3, 4, 5]\nys = xs[:]\nys[0] = 99\nxs                       # [0, 1, 2, 3, 4, 5]  (xs did not change)\ngrid = [[1], [2]]\ncp = grid[:]\ncp[0].append(9)\ngrid                     # [[1, 9], [2]]  (the inner list is shared)',
    'xs = [0, 1, 2, 3, 4, 5]\nxs[4:99]     # [4, 5]\nxs[10:20]    # []  (no items there: an empty list, no error)\n[][:1]       # []  (no error on an empty list; [][0] would raise IndexError)',
]
def _pm_run_earlier(_srcs, _g):
    import sys as _s, io as _io, warnings as _w
    for _src in _srcs:
        _o, _e = _s.stdout, _s.stderr
        _s.stdout = _s.stderr = _io.StringIO()
        try:
            with _w.catch_warnings():
                _w.simplefilter('ignore')
                exec(compile(_src, '<earlier block>', 'exec'), _g)
        except (Exception, SystemExit):
            pass
        finally:
            _s.stdout, _s.stderr = _o, _e
    if 'matplotlib.pyplot' in _s.modules:
        _s.modules['matplotlib.pyplot'].close('all')   # their plots belong to their own blocks
_pm_run_earlier(_pm_earlier, globals())
del _pm_earlier, _pm_run_earlier

Common mistakes

Changing a string through an index or a slice. s[0] = "P" raises TypeError: 'str' object does not support item assignment (full error page). Build a new string: "P" + s[1:].

Start after stop. With a positive step, a slice whose start comes after its stop is empty. Add a step of -1:

python
xs[4:1]         # []
xs[4:1:-1]      # [4, 3, 2]
+ setup added so this can run · re-runs 4 earlier blocks on this page first
xs = ["alpha", "beta", "gamma"]

# Earlier blocks on this page that this one builds on, re-run quietly first.
_pm_earlier = [
    'xs = [0, 1, 2, 3, 4, 5]\nxs[1:4]            # [1, 2, 3]  (index 1, 2 and 3; index 4 is not included)\nxs[:3]             # [0, 1, 2]  (the first 3 items)\nxs[3:]             # [3, 4, 5]  (from index 3 to the end)\nxs[:]              # [0, 1, 2, 3, 4, 5]  (a copy of the whole list)\nxs[:2] + xs[2:]    # [0, 1, 2, 3, 4, 5]  (the two parts join back into the whole list)',
    'xs = [0, 1, 2, 3, 4, 5]\nxs[1:3] = [7, 8, 9]    # [0, 7, 8, 9, 3, 4, 5]  (replaces 2 items with 3 items; the list grows)\nxs[1:4] = []           # [0, 3, 4, 5]  (assigning an empty list removes the items at index 1, 2 and 3)\nxs[:0] = [9]           # [9, 0, 3, 4, 5]  (assigning to an empty slice inserts items)\ndel xs[::2]            # [0, 4]  (del removes every second item)',
    'xs = [0, 1, 2, 3, 4, 5]\nys = xs[:]\nys[0] = 99\nxs                       # [0, 1, 2, 3, 4, 5]  (xs did not change)\ngrid = [[1], [2]]\ncp = grid[:]\ncp[0].append(9)\ngrid                     # [[1, 9], [2]]  (the inner list is shared)',
    'xs = [0, 1, 2, 3, 4, 5]\nxs[4:99]     # [4, 5]\nxs[10:20]    # []  (no items there: an empty list, no error)\n[][:1]       # []  (no error on an empty list; [][0] would raise IndexError)',
]
def _pm_run_earlier(_srcs, _g):
    import sys as _s, io as _io, warnings as _w
    for _src in _srcs:
        _o, _e = _s.stdout, _s.stderr
        _s.stdout = _s.stderr = _io.StringIO()
        try:
            with _w.catch_warnings():
                _w.simplefilter('ignore')
                exec(compile(_src, '<earlier block>', 'exec'), _g)
        except (Exception, SystemExit):
            pass
        finally:
            _s.stdout, _s.stderr = _o, _e
    if 'matplotlib.pyplot' in _s.modules:
        _s.modules['matplotlib.pyplot'].close('all')   # their plots belong to their own blocks
_pm_run_earlier(_pm_earlier, globals())
del _pm_earlier, _pm_run_earlier

Mixing up a slice and an index. xs[-1:] gives a list with one item, but xs[-1] gives the item itself:

python
xs[-1:]         # [5]
xs[-1]          # 5
+ setup added so this can run · re-runs 4 earlier blocks on this page first
# Lightweight mock for objects whose attributes/methods aren't critical
class _AutoMock:
    def __init__(self, name='mock'): self._name = name
    def __getattr__(self, k): return _AutoMock(self._name + '.' + k)
    def __call__(self, *a, **kw):
        print('-> ' + self._name + '() called')
        return _AutoMock(self._name + '()')
    def __repr__(self): return '<mock ' + self._name + '>'
    def __str__(self): return '<mock ' + self._name + '>'
    def __bool__(self): return True
    def __iter__(self): return iter([])
    def __len__(self): return 0
    def __getitem__(self, k): return _AutoMock(self._name + '[...]')
    def __setitem__(self, k, v): pass
    def __enter__(self): return self
    def __exit__(self, *a): return False
    async def __aenter__(self): return self
    async def __aexit__(self, *a): return False
    def __add__(self, o): return self
    def __radd__(self, o): return self
    def __sub__(self, o): return self
    def __mul__(self, o): return self
    def __rmul__(self, o): return self
    def __truediv__(self, o): return self
    def __eq__(self, o): return isinstance(o, _AutoMock)
    def __hash__(self): return hash(self._name)
    def __lt__(self, o): return True
    def __le__(self, o): return True
    def __gt__(self, o): return False
    def __ge__(self, o): return False
    def __mro_entries__(self, bases): return (object,)

xs = _AutoMock('xs')

# Earlier blocks on this page that this one builds on, re-run quietly first.
_pm_earlier = [
    'xs = [0, 1, 2, 3, 4, 5]\nxs[1:4]            # [1, 2, 3]  (index 1, 2 and 3; index 4 is not included)\nxs[:3]             # [0, 1, 2]  (the first 3 items)\nxs[3:]             # [3, 4, 5]  (from index 3 to the end)\nxs[:]              # [0, 1, 2, 3, 4, 5]  (a copy of the whole list)\nxs[:2] + xs[2:]    # [0, 1, 2, 3, 4, 5]  (the two parts join back into the whole list)',
    'xs = [0, 1, 2, 3, 4, 5]\nxs[1:3] = [7, 8, 9]    # [0, 7, 8, 9, 3, 4, 5]  (replaces 2 items with 3 items; the list grows)\nxs[1:4] = []           # [0, 3, 4, 5]  (assigning an empty list removes the items at index 1, 2 and 3)\nxs[:0] = [9]           # [9, 0, 3, 4, 5]  (assigning to an empty slice inserts items)\ndel xs[::2]            # [0, 4]  (del removes every second item)',
    'xs = [0, 1, 2, 3, 4, 5]\nys = xs[:]\nys[0] = 99\nxs                       # [0, 1, 2, 3, 4, 5]  (xs did not change)\ngrid = [[1], [2]]\ncp = grid[:]\ncp[0].append(9)\ngrid                     # [[1, 9], [2]]  (the inner list is shared)',
    'xs = [0, 1, 2, 3, 4, 5]\nxs[4:99]     # [4, 5]\nxs[10:20]    # []  (no items there: an empty list, no error)\n[][:1]       # []  (no error on an empty list; [][0] would raise IndexError)',
]
def _pm_run_earlier(_srcs, _g):
    import sys as _s, io as _io, warnings as _w
    for _src in _srcs:
        _o, _e = _s.stdout, _s.stderr
        _s.stdout = _s.stderr = _io.StringIO()
        try:
            with _w.catch_warnings():
                _w.simplefilter('ignore')
                exec(compile(_src, '<earlier block>', 'exec'), _g)
        except (Exception, SystemExit):
            pass
        finally:
            _s.stdout, _s.stderr = _o, _e
    if 'matplotlib.pyplot' in _s.modules:
        _s.modules['matplotlib.pyplot'].close('all')   # their plots belong to their own blocks
_pm_run_earlier(_pm_earlier, globals())
del _pm_earlier, _pm_run_earlier

xs[:-k] when k is 0. -0 is the same as 0, so xs[:-0] is an empty list, not the whole list. Write xs[:len(xs) - k] when k can be 0:

python
k = 0
xs[:-k]               # []
xs[:len(xs) - k]      # [0, 1, 2, 3, 4, 5]
+ setup added so this can run · re-runs 4 earlier blocks on this page first
xs = ["alpha", "beta", "gamma"]

# Earlier blocks on this page that this one builds on, re-run quietly first.
_pm_earlier = [
    'xs = [0, 1, 2, 3, 4, 5]\nxs[1:4]            # [1, 2, 3]  (index 1, 2 and 3; index 4 is not included)\nxs[:3]             # [0, 1, 2]  (the first 3 items)\nxs[3:]             # [3, 4, 5]  (from index 3 to the end)\nxs[:]              # [0, 1, 2, 3, 4, 5]  (a copy of the whole list)\nxs[:2] + xs[2:]    # [0, 1, 2, 3, 4, 5]  (the two parts join back into the whole list)',
    'xs = [0, 1, 2, 3, 4, 5]\nxs[1:3] = [7, 8, 9]    # [0, 7, 8, 9, 3, 4, 5]  (replaces 2 items with 3 items; the list grows)\nxs[1:4] = []           # [0, 3, 4, 5]  (assigning an empty list removes the items at index 1, 2 and 3)\nxs[:0] = [9]           # [9, 0, 3, 4, 5]  (assigning to an empty slice inserts items)\ndel xs[::2]            # [0, 4]  (del removes every second item)',
    'xs = [0, 1, 2, 3, 4, 5]\nys = xs[:]\nys[0] = 99\nxs                       # [0, 1, 2, 3, 4, 5]  (xs did not change)\ngrid = [[1], [2]]\ncp = grid[:]\ncp[0].append(9)\ngrid                     # [[1, 9], [2]]  (the inner list is shared)',
    'xs = [0, 1, 2, 3, 4, 5]\nxs[4:99]     # [4, 5]\nxs[10:20]    # []  (no items there: an empty list, no error)\n[][:1]       # []  (no error on an empty list; [][0] would raise IndexError)',
]
def _pm_run_earlier(_srcs, _g):
    import sys as _s, io as _io, warnings as _w
    for _src in _srcs:
        _o, _e = _s.stdout, _s.stderr
        _s.stdout = _s.stderr = _io.StringIO()
        try:
            with _w.catch_warnings():
                _w.simplefilter('ignore')
                exec(compile(_src, '<earlier block>', 'exec'), _g)
        except (Exception, SystemExit):
            pass
        finally:
            _s.stdout, _s.stderr = _o, _e
    if 'matplotlib.pyplot' in _s.modules:
        _s.modules['matplotlib.pyplot'].close('all')   # their plots belong to their own blocks
_pm_run_earlier(_pm_earlier, globals())
del _pm_earlier, _pm_run_earlier

Assigning the wrong number of items to a slice with a step. With a step, the slice and the new items must have the same length. xs[::2] = [0] raises ValueError: attempt to assign sequence of size 1 to extended slice of size 3.

Slicing dict keys. d.keys()[:1] raises TypeError: 'dict_keys' object is not subscriptable. Write list(d)[:1].

See also

Practice this

on practicepython.in

Short exercises that run in your browser and tell you what your code did, as well as whether a test passed.

Found this useful? Share it with a friend who’s learning Python.