Learning: Python
Become the engineer trusted to own Python on a team: able to ship a typed, tested, packaged Python service or tool, take a traceback or a profile to its root cause instead of guessing, and say concretely why someone's clever use of Python's dynamism is going to hurt them.
Start here: 0001. Names Are Bindings, Not Boxes
Latest lesson: 0044. Judgment
Success looks like
- Predict what a mutable default argument, a closure over a loop variable, and a shared list do, before running the code.
- Ship a package that installs and runs on a machine that is not the one it was written on.
- Add type hints a checker verifies, and fix what it reports rather than silencing it.
- Write
pytesttests that fail informatively, using fixtures and parametrisation instead of copied test bodies. - Find the slow part with a profiler and prove the fix with a measurement.
- Choose between threads, processes and
asynciofrom the shape of the workload, and state what the GIL does and does not prevent. - Implement a dunder method or a context manager when the data model calls for one, and recognise when a plain function is the better answer.
- Review someone's Python and name precisely why a class, a metaclass, or an inheritance chain is the wrong tool there.
Constraints
- Assumes no prior Python. Experience in another language shortens the early stages but is not required, and it brings habits Python punishes quietly: a class where a function would do, a loop where a comprehension reads better, a defensive copy the language already made.
- Needs only CPython and a terminal on any supported OS. Nothing in the arc requires paid tooling, a cloud account, or a second machine.
- CPython is the reference implementation throughout. Other implementations appear only where behaviour genuinely differs.
- Reps are small programs that fit one sitting. Spacing them across days is the mechanism, not an inconvenience.
- Typing and packaging move faster than the language does. Version-sensitive claims are checked against the current documentation and the relevant PEP, and any lesson that depends on a release says which one.
Out of scope
- Web frameworks as subjects in their own right: Django, FastAPI, Flask.
- The scientific and machine-learning stack as a subject: NumPy, pandas, PyTorch. See
llm/finetuningfor that boundary. - Distribution beyond wheels published to an index: conda, OS packages, frozen single-file binaries.
- CPython internals past the point where they stop predicting program behaviour: bytecode, the C API, writing extension modules.
- Python 2, and migration from it.
The arc
Seven stages, zero to senior. Not a lesson list: a stage takes several lessons, and the boundaries are soft.
| Stage | Lessons | Covers | Done when |
|---|---|---|---|
| 1. Foundations | 0001 to 0007 | Objects and names, mutability and aliasing, lists, dicts, sets, tuples, truthiness, comprehensions, functions and argument passing | Can predict aliasing and mutation without running the code |
| 2. Idiom | 0008 to 0014 | Iterators and generators, context managers, exceptions as control flow, dataclasses, modules and packages, the standard library worth knowing | Reaches for a generator or a context manager rather than hand-rolling the loop and the try/finally |
| 3. Types and tooling | 0015 to 0021 | Annotations, generics, Protocol, checker strictness, linting and formatting, virtual environments, dependency and project management, building a wheel | A strict checker passes, and someone else can install the result |
| 4. The data model | 0022 to 0027 | Dunder methods, __init__ versus __new__, properties, descriptors, class versus instance attributes, method resolution order, why metaclasses are almost never the answer | Implements the protocol a type needs, and can say when not to |
| 5. Testing | 0028 to 0033 | pytest mechanics, fixtures and scope, parametrisation, property-based testing, what deserves a mock and what does not, coverage as a signal rather than a target | Has a test suite that caught a regression before a human did |
| 6. Concurrency and performance | 0034 to 0039 | Threads, processes, asyncio, what the GIL actually serialises, blocking calls inside an event loop, profiling, the cost of attribute lookup and allocation | Chooses the concurrency model from the workload and proves the win from a profile |
| 7. Judgment | 0040 to 0044 | API and package design, deprecation, review, reading the standard library and the PEPs for answers | Trusted to make the call and to explain it to someone else |
Lessons
Work through these in order.
| # | Lesson | Teaches |
|---|---|---|
| 0001 | Names Are Bindings, Not Boxes | Assignment binds a name to an object and never copies it, so two names can mean one thing |
| 0002 | Mutability and Copying | Mutability belongs to the object, so an immutable container can still hold something that changes |
| 0003 | Lists and Slicing | A slice copies, in-place methods return None, and += mutates what + would rebuild |
| 0004 | Dicts and Sets | Keys must be hashable, lookup has one right idiom per intention, and a view is not a snapshot |
| 0005 | Truthiness, None and Equality | Empty is falsy but not None, so the wrong default idiom rejects zero and the empty string |
| 0006 | Functions and Arguments | Defaults are evaluated once at definition, and a caller sees every mutation you make |
| 0007 | Comprehensions | One expression that builds a container, with its own scope and a limit worth respecting |
| 0008 | The Iteration Protocol | Why for works on anything, and why some of those things can only be looped over once |
| 0009 | Generators | A function that pauses, keeps its local state, and produces values only when asked |
| 0010 | Exceptions | Asking forgiveness instead of permission, and catching exactly what you can handle |
| 0011 | Context Managers | A block with a guaranteed exit, and how to write one in six lines |
| 0012 | Dataclasses | Generated init, repr and equality, and choosing the right shape for a bundle of data |
| 0013 | Modules and Packages | A module runs once, an import binds a name, and how the two produce every import error you have seen |
| 0014 | The Standard Library | The modules that delete code you were about to write, and the ones that prevent a dependency |
| 0015 | Annotations Are Claims | The interpreter stores them and never checks them, which is what makes them worth writing |
| 0016 | Making a Checker Useful | Configuring strictness, reading the error codes, and narrowing instead of silencing |
| 0017 | Generics and Protocols | Keeping the element type through a function, and typing a shape instead of a class |
| 0018 | Types at the Boundary | Turning Any from JSON, environment and database rows into something a checker can reason about |
| 0019 | Environments and Dependencies | Isolation per project, the difference between a requirement and a lock, and who pins what |
| 0020 | Building a Package | What a wheel is, what the metadata has to say, and proving the artefact installs |
| 0021 | Lint and Format | Ending the style argument, and the rule families that find real defects |
| 0022 | Attribute Lookup | Where dot notation actually looks, and why a class attribute is shared by every instance |
| 0023 | Properties and Descriptors | Turning an attribute into code without changing a single caller |
| 0024 | Dunder Methods | Which protocols a type should implement, and which ones fall back to others |
| 0025 | Construction | What init does not control, when new is required, and naming your constructors |
| 0026 | Inheritance and the MRO | What super actually does, why the order is computed, and when composition wins |
| 0027 | Metaclasses, and Why Not | What a class statement actually does, and the four hooks that replace almost every metaclass |
| 0028 | What a Test Asserts | One behaviour per test, a failure message that needs no debugger, and plain assert |
| 0029 | Fixtures | Setup as a dependency the test asks for, with teardown that runs whatever happens |
| 0030 | Parametrisation | One test body, many cases, and a failure that names the case that broke |
| 0031 | Test Doubles | What to replace, what to leave alone, and why patch takes the path where the name is used |
| 0032 | Property-Based Testing | Stating what must always hold, and letting a library find the input that breaks it |
| 0033 | Coverage and Confidence | Why 100 per cent proves nothing, what the number is good for, and which tests to trust |
| 0034 | The GIL, Precisely | What it serialises, what it does not protect, and what changes without it |
| 0035 | Threads and Shared State | A pool instead of raw threads, a queue instead of a lock, and the exception nobody saw |
| 0036 | Processes and Interpreters | Real parallelism for computation, and what it costs to send data across a boundary |
| 0037 | asyncio | One thread, thousands of waits, and the blocking call that stalls all of them |
| 0038 | Choosing a Model | One question decides it, and the measurement that settles the rest |
| 0039 | Measuring Before Optimising | Where the time actually goes, and why the micro-optimisations you were taught are gone |
| 0040 | Designing an API | The signature is the contract, and everything observable becomes one whether you meant it or not |
| 0041 | Changing a Published API | Deprecation that users actually see, and what counts as a breaking change |
| 0042 | Reviewing Python | What to look for, in what order, and how to separate a defect from a preference |
| 0043 | Reading the Source | Getting the answer from the standard library and the PEPs when the documentation stops |
| 0044 | Judgment | When to break the rules from the earlier stages, and how to defend the call |
Reference
- Glossary: canonical terms for this topic
- Resources: trusted sources, each annotated with what it covers
- Mutability and copying: which types mutate, what each copy idiom copies, and who can see it
- Iteration and generators: what is consumed once, what survives a second pass, and which itertools tool fits
- Exceptions and cleanup: the hierarchy, what each clause guarantees, and which built-in to raise
- Typing: current spellings, narrowing forms, error codes, and which shape to use at a boundary
- Project and packaging: one pyproject.toml annotated, the specifier grammar, and the checks before publishing
- Data model: attribute lookup order, the dunder map, the descriptor protocol, and which hook replaces a metaclass
- Testing: fixture scopes, parametrisation forms, which double to reach for, and reading a coverage report
- Concurrency and performance: which model for which workload, the measured numbers behind it, and how to read a profile
- API and review: signature decisions, what counts as breaking, the review checklist, and where to find an answer
How this works
The arc is complete: all seven stages are written, from lesson 0001 to lesson 0044. Work through them in order, or start at the stage whose "Done when" you cannot yet demonstrate.
Each lesson is short and self-contained. Answer keys are collapsed: recall first, then open them. The real-world reps matter more than the reading, and spacing them out is the point. Anything still unclear at the end of a lesson is worth chasing to its primary source before moving on.