Python by Example

Conditionals

Python branching with if/elif/else, conditional expressions, and structural pattern matching with match/case (3.10+).

Python's branching uses if, elif, and else. For a quick inline choice there is the conditional expression (a if condition else b). Since Python 3.10 there is also structural pattern matching with match and case, which matches on the shape and contents of a value rather than just equality.

An if/elif/else chain evaluates each condition in order and runs the first branch that is true. elif is short for "else if" - you can have as many as you need. The else branch runs when no earlier condition matched.

def classify_status(code: int) -> str:
    if code < 200:
        return "informational"
    elif code < 300:
        return "success"
    elif code < 400:
        return "redirect"
    elif code < 500:
        return "client error"
    else:
        return "server error"
 
classify_status(200)  # 'success'
classify_status(404)  # 'client error'
classify_status(503)  # 'server error'

The conditional expression (value_if_true if condition else value_if_false) fits a two-way choice on one line. It is useful for short, readable choices. Nesting conditional expressions inside one another is a code-review smell - use a regular if/else block instead.

n = 7
label = "even" if n % 2 == 0 else "odd"
label  # 'odd'
 
# Readable: one level deep
status = "active" if user.is_logged_in else "guest"
 
# Avoid: nested ternaries are hard to read at a glance
# result = "a" if x else ("b" if y else "c")  -- use if/elif/else

match/case (Python 3.10+) is structural pattern matching - it inspects the shape and contents of a value, not just its equality. A case clause can destructure a dict, pull out fields by name, and bind them as local variables in one step. One important detail: case x: with a bare name always binds (it never compares), so use a dotted name like case Color.RED: to compare against a constant.

def handle(event: dict) -> str:
    match event:
        case {"type": "click", "x": x, "y": y}:
            return f"click at ({x}, {y})"
        case {"type": "keypress", "key": key}:
            return f"key: {key}"
        case {"type": str(t)}:
            return f"unknown event type: {t}"
        case _:
            return "malformed event"
 
handle({"type": "click", "x": 10, "y": 20})   # 'click at (10, 20)'
handle({"type": "keypress", "key": "Enter"})   # 'key: Enter'

In production

match is structural pattern matching, not a C-style switch - it matches shape, not just equality. Reach for it on tagged dicts, parsed JSON, AST nodes, and message envelopes where you want to destructure and bind fields in one step; for plain equality dispatch a chain of elif is clearer. case x: with a bare name binds rather than compares - use a dotted name (case Color.RED:) or a guard (case x if x == sentinel:) when you need literal comparison.

Enjoyed this? Get more essays on software craft delivered to your inbox.

Subscribe free