Python Tour

Updated

September 8, 2026

Python Tour

This chapter is a walking tour: names, if/for, lists and dicts, a function, an exception, f-strings. It is not a complete language. Later parts slow down on each piece. No classes, no type hints, no async.

Mental model

A name refers to a value (closer = "Sam"). Values have types (str, int, list, dict) that you can ask for with type, but you do not annotate them yet.

Control flow is if/else to choose, and for to walk a collection.

A list is an ordered sequence. A dict is a map from key to value. Desk data starts here: a ticket is a dict, a shift’s tickets are a list of dicts.

A function is a named piece of work with arguments and a return. An exception is how failure is reported. An f-string (f"...{name}...") is how you build a message from values.

Worked examples

Case 1: Names and f-strings

Save as hours.py.

# hours.py
def main():
    closer = "Sam"
    start = 17
    end = 23
    length = end - start
    print(f"{closer} works {start}:00–{end}:00 ({length} hours)")


if __name__ == "__main__":
    main()

Run:

uv run python hours.py

Output:

Sam works 17:00–23:00 (6 hours)

closer is a string. start, end, and length are integers. The f-string interpolates them in order.

Case 2: if and for over a list of dicts

Save as tickets.py.

# tickets.py
def main():
    tickets = [
        {"id": 1, "status": "open"},
        {"id": 2, "status": "paid"},
        {"id": 3, "status": "open"},
    ]
    for t in tickets:
        if t["status"] == "open":
            print(f"ticket {t['id']} still open")
        else:
            print(f"ticket {t['id']} is {t['status']}")


if __name__ == "__main__":
    main()

Run:

uv run python tickets.py

Output:

ticket 1 still open
ticket 2 is paid
ticket 3 still open

tickets is a list. Each t is a dict. t["id"] looks up a key. for walks the list once. if branches on the status string.

Case 3: A function over order lines

Save as orders.py. The function does one job: quantity times price. main prints the bill.

# orders.py
def line_total(qty, price):
    return qty * price


def main():
    lines = [
        {"item": "soup", "qty": 2, "price": 6},
        {"item": "pie", "qty": 1, "price": 9},
    ]
    total = 0
    for line in lines:
        amount = line_total(line["qty"], line["price"])
        total = total + amount
        print(f"{line['qty']}× {line['item']} = {amount}")
    print(f"total {total}")


if __name__ == "__main__":
    main()

Run:

uv run python orders.py

Output:

2× soup = 12
1× pie = 9
total 21

return sends a value back to the caller. total = total + amount is a running sum. A later chapter will show += and dataclasses. This is enough to bill a table.

Case 4: Raise when the input is nonsense

Save as bad_price.py.

# bad_price.py
def line_total(qty, price):
    if price < 0:
        raise ValueError(f"price {price} cannot be negative")
    return qty * price


def main():
    print(line_total(2, 6))
    print(line_total(1, -9))


if __name__ == "__main__":
    main()

Run:

uv run python bad_price.py

Output (the process exits non-zero):

12
Traceback (most recent call last):
  File "bad_price.py", line 14, in <module>
    main()
    ~~~~^^
  File "bad_price.py", line 10, in main
    print(line_total(1, -9))
          ~~~~~~~~~~^^^^^^^
  File "bad_price.py", line 4, in line_total
    raise ValueError(f"price {price} cannot be negative")
ValueError: price -9 cannot be negative

Your editor may wrap the caret lines. Read the traceback from the bottom: the exception type and message, then the call that caused it. ValueError is the right type when the caller passed a bad value.

The trap

Cramming the tour into one clever line: a lambda, a side-effecting list comprehension, and a print inside an expression. It fits on a slide. It does not fit a desk.

The other trap is reaching for a class the moment you have two dicts. {"id": 1, "status": "open"} is fine. A class arrives when you have behavior that belongs with the data, not before.

The boring rule

  • Names are ordinary words (closer, tickets, line_total).
  • Use f-strings for messages. Do not stitch with + unless you are joining a list.
  • A list of dicts is a valid model for tickets and order lines.
  • Functions return values. They do not print unless printing is the job (main).
  • Raise ValueError (or another specific type) for a bad argument. Do not return "error".
  • Keep if __name__ == "__main__":.

Try this

  1. In hours.py, add a break_minutes integer and print it in the same f-string.
  2. In tickets.py, count how many tickets are "open" and print the count after the loop.
  3. In orders.py, add a third line ("tea", qty 3, price 2) and confirm the total becomes 27.
  4. In bad_price.py, wrap the second line_total call in try/except ValueError and print the error without crashing.