Foundations: Python, Git and toolsLoading progress…

Comprehensions and transformations

Organize a small program into modules and classes while handling files, failures and iteration.

Building a list from another one

A very common job is: take a collection, keep some of its items, change them, and collect the results. With a loop you write four lines every time: make an empty list, loop, test, append. A comprehension says the same thing in one expression. It reads almost like the sentence "the square of x, for every x in nums, if x is greater than 0".

nums = [-1, 2, 3]

result = []                     # loop version
for x in nums:
    if x > 0:
        result.append(x * x)

result = [x * x for x in nums if x > 0]     # comprehension: [4, 9]

The parts always come in this order: [ expression for item in source if condition ]. Python walks the source one item at a time. The if decides whether the item takes part, and the expression decides what goes into the new collection. The original is never changed: you get a new list.

Watch items go through the pipeline

[x * x for x in nums if x > 0]

0

For each item in nums

-1
2
3
-4
5

Result so far

[]

0 of 5 items processed.

The same job as a loop

result = []for x in nums: if x > 0: result.append(x * x)

The if at the end is a filter: items that fail it are dropped and produce nothing.

Press Process next item. Green items pass the filter and produce a value, dashed ones are dropped. Move the slider to change the filter and compare with the loop on the right.

Curly braces work the same way: {w: len(w) for w in words} builds a dict, and {x % 3 for x in nums} builds a set.

Two different ifs

This is the detail that trips almost everyone. An if after the source is a filter: it removes items, so the output can be shorter. An if ... else in front of the for is a conditional expression: it picks a value for each item, so the output is exactly as long as the input. Try the third example above to see all four items survive.

Predict first. What is [v if v > 0 else 0 for v in [-3, 5, -1]]?

Predict first. How many items are in [w for w in ["a", "bb", "ccc"] if len(w) > 1]?

When to stop using one

Do

Use a comprehension to build a collection from one source with a simple expression and at most one filter.

Don't

Cram several nested conditions into one line just because it fits.

Chapter context

As a program grows, the difficult part becomes keeping state and responsibilities understandable. Use simple functions for transformations, classes for related state and behavior, and modules to give those responsibilities names.

Chapter reference: math, worked example & code

This section belongs to the whole chapter, so it is the same on every lesson in it and may cover a different lesson's topic. The code for the experiment you just ran is inside the experiment itself.

Math and notation

Reason about state ownership before adding abstraction. For a transformation f, repeated calls should have independent results unless shared state is explicitly intended. A lazy iterator retains only the state required for its next step; it does not imply that downstream consumers also use constant memory.

Worked example

An inventory starts with 5 items. reserve(2) leaves 3. A second independent inventory still contains 5. If both objects instead refer to the same list, mutating one changes what both observe. Draw object references as arrows to make this distinction explicit.

Reference code

The following example uses the Python standard library and does not call a remote service.

class Inventory:
    def __init__(self, count):
        self.count = count
    def reserve(self, n):
        if n > self.count:
            raise ValueError("insufficient stock")
        self.count -= n
first, second = Inventory(5), Inventory(5)
first.reserve(2)
assert (first.count, second.count) == (3, 5)

Python examples are provided for study and local execution; this page does not execute Python.

Common mistakes

Type annotations are not validation. Catching Exception and continuing can hide corrupt state. Lazy iteration does not make a later list(...) memory-free.