Coding Patterns and Idioms#

This notebook is a recipe reference — practical Python idioms used throughout the book. Return here when you encounter a pattern you want to understand or reuse.

Topics

  • List comprehensions

  • Lambda functions

  • Docstrings and help()

  • Type hints

  • enumerate() and zip()

  • try / except for error handling

  • f-string formatting

List Comprehension and Lambda#

These are concise tools for transformation logic in scripts.

  • Comprehension: readable filtering/transformation in one expression

  • Lambda: lightweight anonymous function defined inline, without def

List Comprehension#

A list comprehension produces a new list by applying an expression to each item in an iterable, with an optional filter condition:

[expression for item in iterable if condition]
names = ['alice', 'bob', 'charlie', 'amy']

# filter: keep names starting with 'a'
starts_with_a = [n for n in names if n.startswith('a')]

# transform: capitalize each name
capitalized = [n.capitalize() for n in names]

# filter + transform combined
short_upper = [n.upper() for n in names if len(n) <= 3]

print(starts_with_a)
print(capitalized)
print(short_upper)
['alice', 'amy']
['Alice', 'Bob', 'Charlie', 'Amy']
['BOB', 'AMY']

Lambda#

A lambda is an anonymous function defined in a single expression. The syntax is:

lambda parameters: expression
  • It takes any number of parameters but only one expression — no statements, no return.

  • The expression is implicitly returned.

  • Lambdas are best used inline, as a key= argument or passed to map()/filter(). For anything more complex, use def instead.

Use case

Example

Single argument

lambda x: x * 2

Multiple arguments

lambda x, y: x + y

As a sorted() key

sorted(data, key=lambda s: len(s))

With map()

list(map(lambda x: x ** 2, nums))

With filter()

list(filter(lambda x: x > 0, nums))

# basic lambda — equivalent to def double(x): return x * 2
double = lambda x: x * 2
print(double(5))           # 10

# multiple arguments
add = lambda x, y: x + y
print(add(3, 4))           # 7

# inline — no assignment needed
print((lambda x: x ** 2)(9))   # 81
10
7
81
names = ['alice', 'bob', 'charlie', 'amy']
nums  = [3, -1, 4, -1, 5, -9, 2, 6]

# sorted(): sort by length
by_length = sorted(names, key=lambda s: len(s))

# map(): square every number
squares = list(map(lambda x: x ** 2, nums))

# filter(): keep only positive numbers
positives = list(filter(lambda x: x > 0, nums))

print(by_length)
print(squares)
print(positives)
['bob', 'amy', 'alice', 'charlie']
[9, 1, 16, 1, 25, 81, 4, 36]
[3, 4, 5, 2, 6]
### Exercise: Comprehension + Lambda
# Given: numbers = [1, 2, 3, 4, 5, 6]
# 1) Use a list comprehension to produce the squares of even numbers.
# 2) Write a lambda that triples a value, then apply it to all numbers with map().
# 3) Use filter() with a lambda to keep only numbers greater than 3.
### Your code starts here.


### Your code ends here.

Hide code cell source

### Solution
numbers = [1, 2, 3, 4, 5, 6]

# 1) squares of even numbers
even_squares = [n ** 2 for n in numbers if n % 2 == 0]

# 2) triple every number using lambda + map
triple = lambda x: x * 3
tripled = list(map(triple, numbers))

# 3) keep only numbers greater than 3
greater = list(filter(lambda x: x > 3, numbers))

print(even_squares)   # [4, 16, 36]
print(tripled)        # [3, 6, 9, 12, 15, 18]
print(greater)        # [4, 5, 6]
[4, 16, 36]
[3, 6, 9, 12, 15, 18]
[4, 5, 6]

Docstrings and help()#

A docstring is a string literal placed immediately after a def (or class) statement. Python attaches it to the object and help() displays it. It is the standard way to document what a function does, what it expects, and what it returns.

def function_name(param):
    """One-line summary of what the function does.

    Longer explanation if needed. Describe parameters and return value.

    Args:
        param: description of the parameter.

    Returns:
        description of the return value.
    """

Write a docstring for every function you intend to reuse or share. It costs almost nothing and saves significant time later.

def add_tax(price, rate=0.07):
    """Return price with sales tax applied.

    Args:
        price: the pre-tax amount (float or int).
        rate: tax rate as a decimal (default 0.07 = 7%).

    Returns:
        float: the post-tax amount.
    """
    return price * (1 + rate)

# help() reads the docstring
help(add_tax)

# __doc__ gives you the raw string
print(add_tax.__doc__)
Help on function add_tax in module __main__:

add_tax(price, rate=0.07)
    Return price with sales tax applied.

    Args:
        price: the pre-tax amount (float or int).
        rate: tax rate as a decimal (default 0.07 = 7%).

    Returns:
        float: the post-tax amount.

Return price with sales tax applied.

Args:
    price: the pre-tax amount (float or int).
    rate: tax rate as a decimal (default 0.07 = 7%).

Returns:
    float: the post-tax amount.

Type Hints#

Type hints annotate function parameters, return values, and sometimes variables with their expected types. Python does not enforce them at runtime — they are documentation for readers and tools like linters, IDEs, and static type checkers.

def greet(name: str) -> str:
    return f"Hello, {name}"

Annotation

Meaning

x: int

parameter x should be an integer

-> str

the function returns a string

-> None

the function returns nothing useful

x: list[int]

a list of integers

x: dict[str, float]

a dictionary with string keys and float values

`x: int

None`

Use type hints for any module-level function you share with others. They act as a lightweight contract that makes code significantly easier to read and debug.

Collection Type Hints#

For collections, put the item type inside square brackets. This says not only that the value is a list, dictionary, tuple, or set, but also what kind of values it contains.

scores: list[int] = [88, 92, 79]
prices: dict[str, float] = {"coffee": 2.50, "tea": 2.00}
point: tuple[float, float] = (3.5, 4.0)
unique_ids: set[int] = {101, 102, 103}

Optional Values and Unions#

Some functions may return either a normal value or None. In modern Python, write that with the union operator |:

def find_score(name: str, scores: dict[str, int]) -> int | None:
    return scores.get(name)

Read int | None as “an integer or None.” More generally, A | B means “either type A or type B”:

def parse_id(text: str) -> int | str:
    if text.isdigit():
        return int(text)
    return text

Older Python code often uses names from the typing module:

from typing import Optional, Union

def find_score_old(name: str, scores: dict[str, int]) -> Optional[int]:
    return scores.get(name)

def parse_id_old(text: str) -> Union[int, str]:
    if text.isdigit():
        return int(text)
    return text

Optional[int] means the same thing as int | None; Union[int, str] means the same thing as int | str.

def add_tax(price: float, rate: float = 0.07) -> float:
    """Return price with sales tax applied."""
    return price * (1 + rate)

def first_word(sentence: str) -> str:
    """Return the first word of a sentence."""
    return sentence.split()[0]

def repeat(items: list, n: int) -> list:
    """Return a new list with items repeated n times."""
    return items * n

print(add_tax(49.99))
print(first_word("hello world"))
print(repeat([1, 2], 3))
53.48930000000001
hello
[1, 2, 1, 2, 1, 2]

enumerate() and zip()#

These two built-ins appear constantly in loops throughout the book.

enumerate(iterable, start=0)#

Returns (index, value) pairs. Use it whenever you need the position of an item while iterating — no manual counter variable needed.

zip(iter1, iter2, ...)#

Pairs up items from multiple iterables, stopping at the shortest. Use it to iterate two related sequences in lockstep — no index needed.

fruits = ['apple', 'banana', 'cherry']
prices = [1.20, 0.50, 2.00]

# enumerate — index + value
for i, fruit in enumerate(fruits):
    print(f"{i}: {fruit}")

print()

# enumerate with a custom start index
for i, fruit in enumerate(fruits, start=1):
    print(f"{i}. {fruit}")

print()

# zip — pair two lists
for fruit, price in zip(fruits, prices):
    print(f"{fruit}: ${price:.2f}")

print()

# zip into a dict
price_map = dict(zip(fruits, prices))
print(price_map)
0: apple
1: banana
2: cherry

1. apple
2. banana
3. cherry

apple: $1.20
banana: $0.50
cherry: $2.00

{'apple': 1.2, 'banana': 0.5, 'cherry': 2.0}

try / except — Error Handling#

When your code does something that might fail — opening a file, converting user input, accessing a key — wrap it in a try block so the program can recover gracefully instead of crashing.

try:
    # code that might raise an exception
except SomeError:
    # what to do when it fails
else:
    # runs only if no exception was raised (optional)
finally:
    # always runs, even if an exception occurred (optional)

Catch specific exceptions — avoid bare except: because it silently swallows every error including bugs.

Common exception

Triggered by

ValueError

Wrong type of value (int("abc"))

TypeError

Wrong type (len(42))

KeyError

Missing dict key

IndexError

List index out of range

FileNotFoundError

File doesn’t exist

ZeroDivisionError

Divide by zero

def safe_divide(a: float, b: float) -> float | None:
    """Divide a by b; return None if b is zero."""
    try:
        return a / b
    except ZeroDivisionError:
        print("Error: cannot divide by zero")
        return None

print(safe_divide(10, 2))    # 5.0
print(safe_divide(10, 0))    # Error message, then None

# ValueError: converting invalid input
def parse_int(text: str) -> int | None:
    """Convert text to int; return None on failure."""
    try:
        return int(text)
    except ValueError:
        print(f"Could not convert {text!r} to int")
        return None

print(parse_int("42"))       # 42
print(parse_int("hello"))    # error message, then None

# FileNotFoundError: reading a missing file
from pathlib import Path

def read_file(path: str) -> str | None:
    """Read a file and return its contents, or None if file not found."""
    try:
        return Path(path).read_text(encoding='utf-8')
    except FileNotFoundError:
        print(f"File not found: {path}")
        return None

print(read_file("nonexistent.txt"))
5.0
Error: cannot divide by zero
None
42
Could not convert 'hello' to int
None
File not found: nonexistent.txt
None

f-String Formatting#

You have used f-strings for basic interpolation (f"Hello, {name}"). They support a rich format spec inside the braces that controls alignment, padding, decimal places, and thousands separators — useful for tables and reports throughout the book.

f"{value:{width}.{precision}{type}}"

Spec

Meaning

Example

Output

{x:10}

right-pad to width 10 (strings left-align by default)

f"{'hi':10}!"

hi        !

{x:>10}

right-align in 10 chars

f"{42:>10}

        42

{x:<10}

left-align

f"{42:<10}

42       

{x:^10}

center

f"{'hi':^10}

   hi  

{x:.2f}

float, 2 decimal places

f"{3.14159:.2f}"

3.14

{x:,.2f}

float with thousands comma

f"{1234567.8:.2f}"

1,234,567.80

{x:08.2f}

zero-padded, width 8

f"{3.14:08.2f}"

00003.14

{x:e}

scientific notation

f"{0.00012:.2e}"

1.20e-04

{x:%}

percentage

f"{0.857:.1%}"

85.7%

items = [
    ('apple',   1.2,   842),
    ('banana',  0.5,  3201),
    ('cherry',  2.0,   150),
]

# aligned table using format specs
print(f"{'Item':<10} {'Price':>8} {'Sold':>8} {'Revenue':>12}")
print('-' * 42)
for name, price, sold in items:
    revenue = price * sold
    print(f"{name:<10} {price:>8.2f} {sold:>8,} {revenue:>12,.2f}")

print()

# percentage and scientific notation
score = 0.857
tiny  = 0.000123
print(f"Accuracy : {score:.1%}")
print(f"Epsilon  : {tiny:.2e}")
Item          Price     Sold      Revenue
------------------------------------------
apple          1.20      842     1,010.40
banana         0.50    3,201     1,600.50
cherry         2.00      150       300.00

Accuracy : 85.7%
Epsilon  : 1.23e-04