AcademyPython
Slicing and Mutability in Python
How start:stop:step slicing works on any sequence, why a slice always copies while an index into a list of lists does not, and where is diverges from ==.
3 min readUpdated 3 exercises
The three numbers in a slice
letters = ['a', 'b', 'c', 'd', 'e']
letters[1:3] # ['b', 'c']
letters[:2] # ['a', 'b'] — start defaults to 0
letters[3:] # ['d', 'e'] — stop defaults to len(letters)
letters[::2] # ['a', 'c', 'e'] — every second element
letters[::-1] # ['e', 'd', 'c', 'b', 'a'] — the whole sequence, reversedstop is exclusive — letters[1:3] takes indices 1 and 2, never 3 — which is what makes letters[:n] + letters[n:] reconstruct the original list for any n, with no overlap and no gap. A negative step walks backward, which is why [::-1] is the idiomatic way to reverse any sequence: string, tuple, or list, all support the same three-number syntax because slicing is part of the sequence protocol, not something lists implement specially.
A slice always returns a new object
original = [1, 2, 3]
copy = original[:]
copy.append(4)
original # [1, 2, 3] — untouchedoriginal[:] — every element, no filtering — is the classic idiom for a shallow copy of a list, and it works because slicing never returns a view into the original in Python the way it does in NumPy; it always builds a new list object. Mutating the copy cannot touch the original for exactly that reason.
Shallow copies stop at one level
matrix = [[1, 2], [3, 4]]
shallow = matrix[:]
shallow[0].append(99)
matrix # [[1, 2, 99], [3, 4]] — the inner list was never copiedThe outer list is new, but its elements are the same inner list objects the original holds references to — a shallow copy copies one level of container and shares everything nested inside it. Reaching shallow[0] and mutating it in place therefore mutates the object matrix[0] also points at. copy.deepcopy is the fix when nested mutable structures need full independence, recursively copying every level instead of stopping at the first.
is checks identity, == checks value
a = [1, 2, 3]
b = [1, 2, 3]
a == b # True — same contents
a is b # False — two different list objects
a is a # True — the same object, trivially== calls __eq__ and compares whatever that method defines as equal — usually contents. is compares object identity: whether two names refer to the literal same object in memory, with no method call involved at all. The confusion usually surfaces with small integers and short strings, which CPython caches and reuses:
x = 5
y = 5
x is y # True — small ints are cached, this is an implementation detail
x = 1000
y = 1000
x is y # False, usually — outside the cached rangeRelying on is for integer or string equality happens to work by accident within the cached range and breaks outside it — == is correct for value comparison regardless of type, and is should be reserved for checking against None, where it is not only idiomatic but the only correct choice, since a custom __eq__ could otherwise make x == None behave unexpectedly.
Mutable types share this risk everywhere, not just in copies
Lists, dicts and sets are mutable; strings, tuples, numbers and frozensets are not. Passing a mutable object into a function and modifying it in place changes the caller's object too, because Python passes object references, not copies — the identical mechanism behind the shallow-copy surprise above and the mutable-default-argument bug elsewhere in this catalogue. Recognising which built-in types are mutable is what predicts, without running the code, whether a given line of Python can have an effect the reader does not see written down anywhere.
Now practice it
2 challenges · 1 quizReading this page is the cheap half. These are the exercises that make you use it.
- Tuples — Pack, Unpack and SwapPacking, unpacking and swapping with tuples — an immutable sequence type, for contrast with lists.ChallengeSolve · EasyChallengeSolve · Easy
- Sets — Set OperationsSet operations exercise the same mutable-type rules from a different built-in collection.ChallengeSolve · EasyChallengeSolve · Easy
- Data Structures & Collections · Collections BasicsCollections basics, including the slicing syntax this page opens on.QuizTake · EasyQuizTake · Easy
Read next
List Comprehensions in Python
How a comprehension differs from a for-loop under the hood, when it trades readability for cleverness, and how the dict and set forms follow the same rule.
3 min
Mutable Default Arguments
Why a default value written as a list or dict is created once, shared across every call, and the None-then-create idiom that every codebase uses to avoid it.
3 min