Python intermediate, one move per page
The twenty-two moves past beginner scripts: classes, exceptions as flow, comprehensions, venvs, type hints, and a project shape your tools already understand.
You write the scripts. These are the moves.
Python intermediate, one move per page
The twenty-two moves past beginner scripts: classes, exceptions as flow, comprehensions, venvs, type hints, and a project shape your tools already understand.
Set in Space Grotesk, Inter and JetBrains Mono (SIL Open Font License).
Language behaviour verified against the official Python documentation: docs.python.org/3 (tutorial, library reference, HOWTOs), typing.python.org, and docs.pytest.org.
Python and the Python logo are trademarks of the Python Software Foundation. This book is an independent guide and is not affiliated with or endorsed by the PSF.
Your purchase is for personal use only. You do not have redistribution rights: please do not share, resell, or republish this book or its pages.
© 2026 Steve Hodgkiss. All rights reserved. Personal use only; no redistribution rights.
Edition 1.0 · stevehodgkiss.net
Contents
Class definitions create objects: the class statement makes a new type, __init__ initializes each new instance, and self is the instance, passed automatically as the first argument of a method. From the Python tutorial, Classes, docs.python.org/3.
Classes: the blueprint
Let's say the script tracks three servers as three copy-pasted dicts. The fourth has a typo in one key and the crash names the line, not the reason.
class Server: makes a type; def __init__(self, host): fills in each new object. self is the instance itself, passed for you. A missing self is the classic first error: TypeError about the argument counts.
The class is the blueprint. Each object is its own house.
Make a Server class with __init__(self, host, cpu), build two, and change one attribute on one of them. Print both.
The try statement: try runs code, except handlers catch named exception classes, else runs when no exception occurred, finally runs on every exit. From the tutorial, Errors and Exceptions. docs.python.org/3.
try, except, else, finally
The script reads a config that's sometimes missing. Half the code is checking, half is the work.
try holds the risky line; except names the error it can handle. else runs only when nothing broke, so the happy path leaves the try. finally runs on every exit, error or not. A bare except: catches everything, including the KeyboardInterrupt trying to stop you.
try the risk, name the error, keep the work in else.
Wrap open("missing.txt") in try/except FileNotFoundError and print a clean message instead of a traceback.
Comprehensions: [expr for x in it if cond] builds a list, {k: v for ...} a dict, {expr for ...} a set, each with an optional filtering if. From the reference, Displays for list, set and dictionary. docs.python.org/3.
Comprehensions
Every script has one: start a list, loop, append, return. Four lines doing one job, and the job's shape is buried in the middle.
[f.clean(n) for n in names if n] builds the list in one readable line. The same shape with braces makes a dict or a set. Two nested fors and a filter in one line stops reading; that's a loop again.
If it reads at a glance, it's a comprehension. If not, it's a loop.
Replace one append loop in your code with [n.strip() for n in lines if n.strip()]. Count the lines you deleted.
Function annotations: def f(x: int) -> str records type hints that are stored but not enforced at runtime; they're consumed by static checkers like mypy. From the tutorial and typing docs. docs.python.org/3.
Type hints: the contract
def process(rows, limit) tells you nothing at the call site six weeks later. List of dicts? Of strings? The docstring guesses.
def process(rows: list[str], limit: int) -> list[dict] is a readable contract. The runtime ignores it: it stores the hints and enforces nothing. mypy reads them and reports the wrong types before the code runs. Passing limit="10" still works at runtime, and still breaks two functions later.
The runtime ignores hints. Your reader doesn't.
Annotate one function with types, then run mypy on the file (pip install mypy) and pass it one wrong argument on purpose.
The script skeleton: a docstring stating purpose, imports at the top, constants in one place, functions above a main() guarded by if __name__ == "__main__":. The ordering taught across the tutorial's module guidance. docs.python.org/3.
The script skeleton
Twenty-one moves, and you use maybe eight on every file. Here is the order they go in.
Docstring, imports, constants, functions, then main() under the __main__ guard. Check your arguments and paths before the work starts. Scripts that grow bottom-up end with imports between functions and constants nobody can find.
The docstring is the first line of documentation you'll ever write. Write it first, actually.
Rebuild one script of yours as the skeleton: docstring, imports, constants, one function, main under the guard. Nothing else.