Lists and Mutable Sequences
A Python list is a mutable sequence of object references. It preserves
order, accepts mixed value types, and supports integer indexing and slicing.
names = ["Ada", "Grace", "Linus"]
first = names[0]
last = names[-1]
middle = names[1:2] # a new, shallow list
Mutation and aliasing
Assignment binds another name to the same list; it does not copy the list.
original = [[1], [2]]
alias = original
shallow = original.copy()
alias.append([3]) # changes original too
shallow[0].append(9) # nested list is still shared
Use copy.deepcopy only when recursively duplicating the complete object graph
is genuinely the intended ownership model. Clear ownership is usually easier to
reason about than defensive deep copying.
Common mutations have distinct contracts:
items.append(value) # one value at the end
items.extend(iterable) # every value from an iterable
items.insert(index, value)
last = items.pop() # remove and return
items.remove(value) # first equal value; ValueError if absent
items[1:3] = replacements # slice assignment may change length
Do not structurally mutate a list while iterating over it unless the behavior is deliberate. Iterate over a copy or build a new result instead.
Comprehensions and generators
Use a list comprehension for a readable transform or filter:
squares = [number * number for number in numbers if number >= 0]
A comprehension constructs the entire list. Use a generator expression when values can be consumed lazily:
total = sum(number * number for number in numbers)
Avoid deeply nested comprehensions; an ordinary loop communicates multi-step state changes more clearly.
Typical costs
CPython implements lists as resizable arrays of references; this is an implementation detail, not a required memory layout for every Python implementation. The table describes that cost model, assuming constant-time element comparisons; custom equality methods can add further cost.
Use collections.deque for frequent operations at both ends. Use a set or
dict when membership or keyed lookup dominates. A tuple expresses a fixed
sequence, but immutability of the tuple does not make referenced objects
immutable.
Repetition and in-place results
The sequence contracts specify that repetition repeats references, not copies of nested objects:
rows = [[0] * 2] * 3
rows[0][0] = 9
assert rows == [[9, 0], [9, 0], [9, 0]]
independent = [[0] * 2 for _ in range(3)]
independent[0][0] = 9
assert independent == [[9, 0], [0, 0], [0, 0]]
numbers = [3, 1, 2]
assert sorted(numbers) == [1, 2, 3]
assert numbers == [3, 1, 2]
assert numbers.sort() is None
assert numbers == [1, 2, 3]
alias = numbers
numbers += [4]
assert alias == [1, 2, 3, 4]
numbers = numbers + [5]
assert alias == [1, 2, 3, 4]
Methods such as append, extend, reverse, and sort mutate the list and
return None; do not write numbers = numbers.sort(). sorted returns a new
list and accepts any iterable. Both sorts are stable: equal sort keys retain
their original relative order. Use key= to choose a comparable property when
the elements themselves cannot be ordered.
Indexing an empty list or calling its pop() raises IndexError; a slice such
as items[:10] safely clips its bounds. del items[index] removes by position,
whereas remove(value) searches for the first equal value. Extended slice
assignment with a step other than one requires exactly as many replacement
items as selected positions.
Use Python Tutor for the repeated-row example: compare [[0] * 2] * 3 with [[0] * 2 for _ in range(3)], then change one inner element. Count the inner list objects, not just the three outer slots, to explain why one version changes several rows.