An intermediate-to-advanced book for experienced programmers. It opens with a fast introduction for programmers coming from other languages.
The latest release, rebuilt from the current book source:
- EPUB with color syntax highlighting, for phone and tablet reading apps.
- EPUB for e-ink readers, which marks code with bolding instead of color.
- Step-by-step guides, simplest way first, to reading it on a Kindle, an iPad, an Android phone or tablet, a computer, or a Kobo or other EPUB e-reader.
All versions are on the releases page.
Every listing in the book is a real file that runs. You will find it in
Examples/, in its chapter's directory, under the name the book gives it.
The answers to the exercises are in Solutions/.
| Directory | What is in it |
|---|---|
Examples/ |
The book's listings, one directory per chapter (Examples/07_Foundations--Classes/), each file named the way the book names it (property_setter.py). |
Examples/utils/ |
Helpers that several chapters import, such as display.py and benchmark.py. Not a chapter. |
Solutions/ |
Worked answers to the exercises, one Markdown file per chapter, numbered to match that chapter's exercise list. |
SolutionsCode/ |
The solution listings extracted to .py files, the same way Examples/ is. |
Chapters/ |
The book itself, one Markdown file per chapter. |
The build generates both code trees from Chapters/ and Solutions/,
where each listing is a fenced python block whose first line is a
# name.py comment. The code you read is therefore the code that runs.
The file is byte for byte what the book prints. A #: comment holds the
output of the statement above it:
c = Circle(10)
print(c.radius)
#: 10
print(c.area)
#: 314.159The build runs every listing and compares its stdout against these markers, so a marker in the book always says what the code prints.
Each chapter ends with exercises that name the listing to start from
("Add a method shrink(self, factor) to Circle in property_setter.py").
Copy that file, change it, run it. Then compare with the numbered answer in
that chapter's Solutions/ file. Each solution is self-contained: it
repeats whatever it needs from the chapter rather than importing it, so you
can read or run one on its own.
make sync regenerates Examples/ and SolutionsCode/ from the Markdown,
discarding any edits you made there. Experiment in them freely, but keep
anything you want to save outside those two trees.
You'll need to do this to experiment with the examples and exercises.
- Clone this repository.
- You need a
makecommand. This is preinstalled on Linux and macOS (macOS: install Xcode Command Line Tools if it's missing). For Windows:winget install ezwinports.make - Install uv.
- Run
uv synconce. This creates.venvand installs the pinned Python (3.15+) and the dev tools automatically. - Run
make tools-checkto verify the essential tools.
That is everything you need to run and test the examples and the solutions.
make doctor diagnoses the two environment problems that bite in practice:
a stale uv stuck on an old Python prerelease, and (on Windows) a process
holding .venv open.
Type make to see every target. In a terminal it opens a picker instead:
arrow keys choose, Enter runs, and ? shows a target's full documentation.
Building the book itself needs more. make site, make local, and
make serve need pandoc on your PATH, make pdf also needs typst, and
make prose needs the standalone vale binary. make tools-check-full
checks for all of them. See
tools/README
for details and install links.
The commands below rebuild build/examples/ and build/solutions/ from
the Markdown chapters, so they always test the current book, never a stale
copy.
| Command | What it does |
|---|---|
make run |
Executes every example file and reports failures. |
make test |
Runs the book's pytest examples, the test_*.py files. |
make ty |
Type-checks every example. Must come out clean. |
make lint |
PEP8-lints every example with ruff. Must come out clean. |
make solutions-run |
Executes every extracted solution and reports failures. |
make solutions-test |
Runs the solutions' pytest examples. |
make solutions-ty, make solutions-lint |
The same two checks over build/solutions/. |
make solutions-gate |
Every solutions check at once: exercise numbering, drift, output markers, types, lint, runs, tests. |
make gate |
Every check over both trees. Run this before committing. |
A first run also pays for downloading the pinned Python and the dev tools.
To run one example instead of all of them, see Run one example by hand below.
A few examples cannot run unattended because they open a window, wait for
input, or loop forever. make run reports those as "Can't run unattended"
rather than as failures. tools/data/norun.txt lists them. Run one by hand
to watch it work.
make gate runs the solutions checks first, as a prerequisite, so a
failure there hides every Chapters/ failure behind it. make sweep runs
everything and reports them all instead of stopping at the first. gate
also refreshes generated content in place: it rewraps prose to one sentence
per line, and it rewrites any #: marker whose listing now prints
something else. Expect git diff Chapters/ to show both.
make check-ch CH=07 runs the whole code-example gate against one chapter
instead of all 47: extract, output markers, listing format, types, lint,
tests. CH takes a number or a filename stem. Make this your edit loop.
Only make gate catches breakage across chapters.
make run-one runs any single example from the repo root and shows its
output. F takes the file's name, or as much of its path as you care to
type:
make run-one F=deque_timing
make run-one F=Examples/07_Foundations--Classes/property_setter.pyIt sets up what the example expects, and prints the commands it stood in
for, because those are what you type when make is not at hand:
cd Examples/03_Foundations--Containers
PYTHONPATH=../utils uv run python deque_timing.pyBoth lines matter. An example reads its sibling modules and data files by
relative path, so it must run from its own chapter directory. About 70 of
them also import a shared helper that lives in Examples/utils
(from benchmark import report), so that directory has to be on the
import path. Miss the second line and Python says:
ModuleNotFoundError: No module named 'benchmark'
In PowerShell the PYTHONPATH line is $env:PYTHONPATH = "../utils";
make run-one prints whichever form fits your shell.
Use uv run python, not a bare python. A python already on your PATH is
usually an older release, and these examples use Python 3.15 syntax.
I started this book in 2008 and after a few years it kind of drifted to a stop. I think part of the problem was that I wanted to move the design patterns work I had done in Java into Python and even then I was beginning to become uncertain about OOP (The material is still there, translated, but it is preceded by a chapter explaining my OOP misgivings).
I had forgotten about this book but (especially at Pycons) people would occasionally come up to me and mention that they had gotten some value out of it. Because of the condition of the book, which still had a number of examples that were still in Java (!), I found this embarrassing.
In June 2026 I decided to see what the Claude AI could do with it, and in short order it had brought everything up to Python 3.15, with type annotations, passing standards checkers, cleaning up prose, etc. I began going back through my Pycon presentations and blog posts and adding those. At the moment it is in decent shape and you can read it online: https://bruceeckel.github.io/ThinkingInPython/
This is for my own bookkeeping.
| Chapter | Edit State |
|---|---|
| 01_Introduction.md | |
| 02_Foundations--Tour.md | |
| 03_Foundations--Containers.md | |
| 04_Foundations--Control_Flow.md | |
| 05_Foundations--Functions.md | |
| 06_Foundations--Modules_and_Packages.md | |
| 07_Foundations--Classes.md | |
| 08_Foundations--Static_Types.md | |
| 09_Foundations--Class_Attributes.md | |
| 10_Foundations--Cleanup.md | |
| 11_Techniques--Testing.md | |
| 12_Techniques--Data_Classes_as_Types.md | |
| 13_Techniques--Pattern_Matching.md | |
| 14_Techniques--Decorators.md | |
| 15_Techniques--Context_Managers.md | |
| 16_Techniques--Comprehensions.md | |
| 17_Techniques--Metaprogramming.md | |
| 18_Techniques--Performance.md | |
| 19_Techniques--Concurrency.md | |
| 20_Patterns--Rethinking_Objects.md | |
| 21_Patterns--Design_Patterns.md | |
| 22_Patterns--Data_Transfer_Objects.md | |
| 23_Patterns--Iterators.md | |
| 24_Patterns--Singleton.md | |
| 25_Patterns--Template_Method.md | X |
| 26_Patterns--Surrogate.md | X |
| 27_Patterns--Factory.md | O |
| 28_Patterns--Function_Objects.md | r |
| 29_Patterns--Changing_the_Interface.md | |
| 30_Patterns--Observer.md | |
| 31_Patterns--State_Machines.md | |
| 32_Patterns--Multiple_Dispatching.md | |
| 33_Patterns--Visitor.md | |
| 34_Patterns--Composite_and_Interpreter.md | |
| 35_Patterns--Flyweight.md | |
| 36_Patterns--Memento.md | |
| 37_Patterns--Pattern_Refactoring.md | |
| 38_Patterns--Simulation.md | |
| 39_Patterns--Pattern_Catalog.md | |
| 40_Functional--Foundations.md | |
| 41_Functional--Toolkits.md | |
| 42_Functional--Error_Handling.md | |
| 43_Functional--Confidence.md | |
| 44_Effects--Effect_Management.md | |
| 45_Effects--Generators.md | |
| 46_Effects--Stateless.md | |
| 47_Effects--Stateless_in_Practice.md |
