Skip to main content

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 array or deque

Built-in sequences at a glance​

Two useful splits:

SplitMeaningExamples
ContainerStores references to Python objects (any types, nesting OK)list, tuple, collections.deque
FlatStores raw values in one compact blockstr, bytes, array.array
MutableYou can change contents in placelist, bytearray, array.array, deque
ImmutableContents cannot be replaced in placetuple, 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.

tip

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​

Quiz

Kinds of sequences

1 / 2

Which 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
caution

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​

Quiz

List comprehensions

1 / 2

What 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​

Quiz

Generator expressions

1 / 2

tuple(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:

  1. Record — a fixed number of fields with meaning by position (lat, lon).
  2. 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​

Quiz

Tuples and unpacking

1 / 2

After 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​

Quiz

Sequence patterns

1 / 2

case [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​

Quiz

Slices

1 / 2

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

TypeUse it when
array.arrayMany numbers, same machine type, you care about memory
collections.dequeFast 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​

Quiz

array and deque

1 / 2

array.array is a good fit for

Pick an answer to see if you are right.


Chapter summary​

Key takeaways
  1. Sequences share iteration, slicing, and len. Split them by container vs flat and mutable vs immutable.
  2. Listcomps build lists; genexps stay lazy. Do not use a listcomp for side effects.
  3. Tuples are records or frozen lists. Nested mutable objects can still change.
  4. Unpacking (* included) and match on sequences make shape explicit.
  5. Slice assignment edits a mutable sequence in the middle without an extra loop.
  6. Reach for array or deque when list is the wrong performance story.

Exercises​

Activity: Letter pairs

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.