Python Tour
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.pyOutput:
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.pyOutput:
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.pyOutput:
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.pyOutput (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
returnvalues. 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
- In
hours.py, add abreak_minutesinteger and print it in the same f-string. - In
tickets.py, count how many tickets are"open"and print the count after the loop. - In
orders.py, add a third line ("tea", qty3, price2) and confirm the total becomes27. - In
bad_price.py, wrap the secondline_totalcall intry/except ValueErrorand print the error without crashing.