An Array of Sequences
Source: Fluent Python, 2nd Edition — Chapter 2 by Luciano Ramalho
Strings, lists, byte strings, arrays, and many library types all speak the same sequence language: you can iterate, slice, concatenate, and ask for len. Python inherited that idea from ABC, the teaching language Guido van Rossum worked on before Python. If you learn the shared interface once, you stop rewriting the same loops for every new type.
This chapter is about using sequences that already exist. Building your own sequence class (with __getitem__ and friends) is a later topic.
We will cover:
- Container sequences vs flat sequences, and mutable vs immutable
- List comprehensions and generator expressions
- Tuples as records vs tuples as frozen lists
- Unpacking and simple sequence patterns (
match) - Reading and writing slices
- When to reach for
arrayordeque
Built-in sequences at a glance
Two useful splits:
| Split | Meaning | Examples |
|---|---|---|
| Container | Stores references to Python objects (any types, nesting OK) | list, tuple, collections.deque |
| Flat | Stores raw values in one compact block | str, bytes, array.array |
| Mutable | You can change contents in place | list, bytearray, array.array, deque |
| Immutable | Contents cannot be replaced in place | tuple, str, bytes |
A tuple of three floats is several Python objects (the tuple plus each float). An array.array('d', ...) of three doubles is one object holding three machine floats. Flat types win on memory; they cannot hold arbitrary objects.
list and tuple do not subclass collections.abc.Sequence in the usual inheritance sense, but they still pass issubclass(..., Sequence) because they are registered as virtual subclasses. You can treat them as sequences in everyday code.
When you meet a new sequence type, ask: container or flat? mutable or not? Then reuse what you already know about list / tuple / str.
🧪 Container vs compact numbers
Mini quiz — kinds of sequences
Kinds of sequences
1 / 2Which is a flat sequence?
Pick an answer to see if you are right.
List comprehensions
A list comprehension (listcomp) always builds a new list. A for loop can do anything: print, count, mutate. That is why a listcomp is often clearer when the only goal is “make this list.”
Keep them short. If the expression needs a comment to explain, a loop is usually kinder.
words = ["red", "green", "blue"]
lens = [len(w) for w in words]
# [3, 5, 4]
You can filter with if:
lens = [len(w) for w in words if len(w) > 3]
# [5, 4]
Line breaks inside [] are fine. A trailing comma in a multi-line list literal is allowed and makes diffs easier.
🧪 Listcomp vs loop
Do not use a listcomp only for side effects (print inside the expression). If you do not need the list, use a for loop or a generator.
Mini quiz — list comprehensions
List comprehensions
1 / 2What does this produce?
[n * 2 for n in [1, 2, 3]]
Pick an answer to see if you are right.
Challenge: even squares
Read integers. Print the squares of the even values, space-separated, using a list comprehension.
Generator expressions
A generator expression (genexp) looks like a listcomp but uses (). It does not build the whole list in memory. It yields items as you iterate.
Use a genexp when you will feed another constructor or a reducing function:
letters = "abc"
print(tuple(ord(ch) for ch in letters))
print(sum(n * n for n in range(4)))
If you wrap a genexp in a function call, one pair of parentheses is enough: sum(n * n for n in range(4)).
🧪 Genexp into tuple and sum
Mini quiz — generator expressions
Generator expressions
1 / 2tuple(x for x in [1, 2]) creates a tuple without first making a full extra list. The expression in the middle is a
Pick an answer to see if you are right.
Challenge: codes as a tuple
Read a word. Return a tuple of ord values using a generator expression (not a listcomp).
Tuples: records or frozen lists?
A tuple can play two roles:
- Record — a fixed number of fields with meaning by position (
lat,lon). - Immutable sequence — like a list you promised not to grow or replace.
Unpacking makes the record role obvious:
city, year, pop = ("Tokyo", 2003, 33_000_000)
* can capture a rest:
head, *body, tail = [1, 2, 3, 4, 5]
# head=1, body=[2, 3, 4], tail=5
A tuple is immutable as a container: you cannot do t[0] = .... If an element is a list, that inner list can still change. t then looks “the same” as a tuple object, but its nested data moved. hash(t) fails if any element is unhashable.
🧪 Unpacking and a nested list
Mini quiz — tuples and unpacking
Tuples and unpacking
1 / 2After a, *rest, b = [10, 20, 30, 40], rest is
a, *rest, b = [10, 20, 30, 40]
Pick an answer to see if you are right.
Challenge: first and last words
Read words. Print the first word, then the last word (one space). Use unpacking with *.
Sequence patterns (match)
Python 3.10+ can match the shape of a sequence:
match parts:
case [name, qty]:
...
case [name, qty, color]:
...
case _:
...
Patterns can nest and bind names. Start with length and a few literals. You do not need every match feature on day one.
🧪 match on a command
Mini quiz — sequence patterns
Sequence patterns
1 / 2case [x, y]: matches which input?
Pick an answer to see if you are right.
Challenge: tiny command
Read a line of tokens. If it is add a b, print the integer sum. If it is neg a, print the negation. Otherwise print ?. Use match.
Slices: read and write
Slicing uses start:stop:step. The stop index is excluded. Omitting start or stop means “from the beginning” or “through the end.” Negative indexes count from the end.
For mutable sequences, a slice can appear on the left of assignment. The right-hand side must be an iterable. Lengths do not have to match: you can shrink or grow the middle of a list.
letters = list("abcde")
letters[1:4] = ["X"]
# ['a', 'X', 'e']
Assigning to a slice of a tuple or str fails: those types are immutable.
🧪 Slice assignment
Mini quiz — slices
Slices
1 / 2What is letters[1:3]?
letters = ["a", "b", "c", "d"]
Pick an answer to see if you are right.
Challenge: keep the ends
Read integers. Replace the middle (everything except first and last) with nothing. Print the two remaining numbers. Use slice assignment. Input always has at least two numbers.
Specialized sequences
When list is the wrong default:
| Type | Use it when |
|---|---|
array.array | Many numbers, same machine type, you care about memory |
collections.deque | Fast appends/pops on both ends (queues, sliding windows) |
deque is not a full sequence: it does not support slicing. It is still iterable and has appendleft / popleft in O(1).
from collections import deque
q = deque([1, 2, 3], maxlen=3)
q.append(4) # 1 falls off the left if maxlen is hit
Named tuples belong with dataclasses in a later chapter; skip them here.
Mini quiz — array and deque
array and deque
1 / 2array.array is a good fit for
Pick an answer to see if you are right.
Chapter summary
- Sequences share iteration, slicing, and
len. Split them by container vs flat and mutable vs immutable. - Listcomps build lists; genexps stay lazy. Do not use a listcomp for side effects.
- Tuples are records or frozen lists. Nested mutable objects can still change.
- Unpacking (
*included) andmatchon sequences make shape explicit. - Slice assignment edits a mutable sequence in the middle without an extra loop.
- Reach for
arrayordequewhenlistis the wrong performance story.
Exercises
Two words on one line. Print every pair a+b for a in the first word and b in the second, space-separated. Use a nested list comprehension.