diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f1e7bb8..c3c3c5e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -62,6 +62,20 @@ make verify-examples
make check-generated
```
+## Quality checks
+
+Five scripts enforce the catalog-level rules from `docs/example-quality-rubric.md`. Run them together with `make quality-checks`.
+
+| Script | What it gates |
+| --- | --- |
+| `scripts/check_registry_integrity.py` | Every owner slug in `docs/quality-registries.toml` exists in `manifest.toml`; tokens are present. |
+| `scripts/check_confusable_pairs.py` | Each confusable pair's owning page contains every token that signals the contrast. |
+| `scripts/check_broad_surface_tours.py` | Each broad-title page either covers every required form or sets `scope_first_pass = true` with `see_also` links to focused neighbors. |
+| `scripts/check_footgun_coverage.py` | Each canonical Python footgun has a page that contains both broken-form and fixed-form tokens. |
+| `scripts/check_notes_supported.py` | Every `:::note` bullet shares at least one keyword with the page body, so notes cannot assert behavior the page never demonstrates. |
+
+The single source of truth for the registries is `docs/quality-registries.toml`. Add a new pair, broad tour, or footgun there, then update the owning page so the tokens appear in cells or prose.
+
## Style expectations
- Keep examples compact and language-tour focused.
diff --git a/Makefile b/Makefile
index c03e35c..3c43e86 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-.PHONY: test embed-examples build check-generated fingerprint browser-layout-test seo-cache-lint verify-examples format-examples verify-python-version verify dev deploy lint
+.PHONY: test embed-examples build check-generated fingerprint browser-layout-test seo-cache-lint verify-examples check-registry-integrity check-confusable-pairs check-broad-surface-tours check-footgun-coverage check-notes-supported quality-checks format-examples verify-python-version verify dev deploy lint
test:
python3 -m unittest discover -s tests -v
@@ -23,6 +23,23 @@ seo-cache-lint:
verify-examples: build
scripts/verify_examples.py
+check-registry-integrity:
+ scripts/check_registry_integrity.py
+
+check-confusable-pairs:
+ scripts/check_confusable_pairs.py
+
+check-broad-surface-tours:
+ scripts/check_broad_surface_tours.py
+
+check-footgun-coverage:
+ scripts/check_footgun_coverage.py
+
+check-notes-supported:
+ scripts/check_notes_supported.py
+
+quality-checks: check-registry-integrity check-confusable-pairs check-broad-surface-tours check-footgun-coverage check-notes-supported
+
format-examples:
scripts/format_examples.py
@@ -32,7 +49,7 @@ verify-python-version: build
lint:
uv run ruff check src tests scripts
-verify: build test seo-cache-lint verify-examples browser-layout-test lint check-generated
+verify: build test seo-cache-lint verify-examples quality-checks browser-layout-test lint check-generated
dev:
uv run pywrangler dev --port 9696
diff --git a/docs/example-quality-rubric.md b/docs/example-quality-rubric.md
index eccfd95..00681a7 100644
--- a/docs/example-quality-rubric.md
+++ b/docs/example-quality-rubric.md
@@ -70,6 +70,62 @@ Flag these during review even when the code is correct:
- The page has no editorial progression: examples are technically related but ordered like a checklist rather than a learning path.
- The page reduces so aggressively that a necessary edge case or contrast disappears.
- `See also` links behave like tags instead of prerequisite, neighbor, or next-depth graph edges.
+- A claim in the `:::note` block is not demonstrated by a cell on the same page.
+- A confusable pair listed in the registry has only one side shown on its owning page.
+- A canonical Python footgun listed in the registry has no page that shows the broken case and the fix.
+- Pages whose titles differ only by a suffix or modifier (`iterators` vs `iterating-over-iterables`, `generators` vs `generator-expressions`) assert the relationship in prose without a cell that demonstrates it.
+
+## Confusable-pair index
+
+Each pair below names two or three concepts learners commonly confuse, and the single page that owns the contrast. The owning page must show both (or all) sides in cells, not merely mention them in prose. `scripts/check_confusable_pairs.py` enforces this.
+
+| Pair | Owning page |
+| --- | --- |
+| `__str__` / `__repr__` | `special-methods.md` |
+| `is` / `==` | `equality-and-identity.md` |
+| list / tuple | `tuples.md` |
+| `@classmethod` / `@staticmethod` / instance method | `classmethods-and-staticmethods.md` |
+| `isinstance()` / `type() ==` | `runtime-type-checks.md` |
+| generator / class iterator | `generators.md` |
+| iterator / iterable | `iterator-vs-iterable.md` |
+| mutable / immutable class attributes | `classes.md` |
+| eager / lazy production | `generators.md` |
+| `Protocol` / `ABC` | `abstract-base-classes.md` |
+| `dataclass` / `NamedTuple` / `TypedDict` | `structured-data-shapes.md` |
+| bound / unbound methods | `bound-and-unbound-methods.md` |
+| `yield` / `return` | `generators.md` |
+| shallow / deep copy | `copying-collections.md` |
+| sync / async functions | `async-await.md` |
+
+## Broad-surface checklists
+
+For each title that names a broad surface area, the page must touch every form below or scope itself down explicitly with a `see_also` link to a focused neighbor. `scripts/check_broad_surface_tours.py` enforces this.
+
+- **Special Methods** (`special-methods.md`): `__init__`, `__repr__`, `__str__`, `__eq__`, `__hash__`, `__lt__`, `__len__`, `__iter__`, `__contains__`, `__getitem__`, `__setitem__`, `__call__`, `__enter__`/`__exit__`, `__bool__`.
+- **Operators** (`operators.md`): arithmetic, comparison, identity (`is`), membership (`in`), boolean short-circuit (`and`/`or`), bitwise, walrus (`:=`).
+- **Type Hints** (`type-hints.md`): scalar annotations, container generics (`list[int]`), `|` unions, `Optional`, function signatures, `TypeAlias`, runtime visibility note.
+- **Testing** (`testing.md`): `unittest.TestCase`, `assertEqual`/`assertRaises`, fixtures or `setUp`, parametrized cases or sub-tests, discovery convention.
+- **Async Await** (`async-await.md`): `async def`, `await`, `asyncio.run`, `asyncio.gather`, `async for`, `async with`.
+- **Packages** (`packages.md`): package layout, `__init__.py`, relative vs absolute imports, `__all__`, namespace packages.
+- **Regular Expressions** (`regular-expressions.md`): `re.match`/`re.search`/`re.findall`, groups, named groups, `re.compile`, flags such as `re.IGNORECASE`/`re.MULTILINE`, substitution.
+- **Literals** (`literals.md`): integer (decimal/hex/binary/underscored), float, string (raw/bytes/f-string), boolean, `None`, container literals.
+
+## Footgun coverage
+
+Each canonical Python surprise below must have a page that demonstrates the broken case and the fix. `scripts/check_footgun_coverage.py` enforces this.
+
+| Footgun | Owning page |
+| --- | --- |
+| Mutable default class attribute (`items = []` on the class body) | `classes.md` |
+| Mutable default function argument (`def f(items=[])`) | `functions.md` |
+| Late-binding closure in a loop | `closures.md` |
+| Integer identity caching (`is` for small ints) | `equality-and-identity.md` |
+| Shallow vs deep copy on nested mutable structures | `copying-collections.md` |
+| Generator one-pass exhaustion | `generators.md` |
+| Dictionary mutation during iteration | `dicts.md` |
+| Floating-point equality | `numbers.md` |
+| `bool` as a subclass of `int` | `booleans.md` |
+| Bare `except` swallowing `KeyboardInterrupt` / `SystemExit` | `exceptions.md` |
## Strengthening checklist
@@ -94,3 +150,5 @@ Before publishing or substantially editing an example, ask:
17. For broad pages, is this a map with categories and links, or should it be split?
18. Do edge cases appear close enough to the main idea that readers understand the boundary?
19. Do `See also` links express prerequisite, neighbor, or next-depth relationships rather than tags?
+20. Is every claim in the `:::note` block demonstrated by a cell on this page?
+21. If this page's title appears in the confusable-pair index or footgun registry, does the page show both sides (or both the broken and fixed forms)?
diff --git a/docs/quality-registries.toml b/docs/quality-registries.toml
new file mode 100644
index 0000000..1ac40cc
--- /dev/null
+++ b/docs/quality-registries.toml
@@ -0,0 +1,180 @@
+# Quality registries.
+#
+# Source of truth for the rubric checks in `scripts/check_*.py`.
+# Each entry pins a contrast or footgun to a single owning page, so the
+# catalog has exactly one home for the lesson and verifiers can prove it.
+
+[[confusable_pairs]]
+name = "__str__ vs __repr__"
+owner = "special-methods"
+tokens = ["__str__", "__repr__"]
+
+[[confusable_pairs]]
+name = "is vs =="
+owner = "equality-and-identity"
+tokens = [" is ", "=="]
+
+[[confusable_pairs]]
+name = "list vs tuple"
+owner = "tuples"
+tokens = ["list", "tuple"]
+
+[[confusable_pairs]]
+name = "classmethod vs staticmethod vs instance method"
+owner = "classmethods-and-staticmethods"
+tokens = ["@classmethod", "@staticmethod", "self"]
+
+[[confusable_pairs]]
+name = "isinstance vs type=="
+owner = "runtime-type-checks"
+tokens = ["isinstance(", "type("]
+
+[[confusable_pairs]]
+name = "generator vs class iterator"
+owner = "generators"
+tokens = ["yield", "__next__"]
+
+[[confusable_pairs]]
+name = "iterator vs iterable"
+owner = "iterator-vs-iterable"
+tokens = ["iterable", "iterator", "iter("]
+
+[[confusable_pairs]]
+name = "mutable vs immutable class attributes"
+owner = "classes"
+tokens = ["class attribute", "__init__"]
+
+[[confusable_pairs]]
+name = "eager vs lazy production"
+owner = "generators"
+tokens = ["return", "yield"]
+
+[[confusable_pairs]]
+name = "Protocol vs ABC"
+owner = "abstract-base-classes"
+tokens = ["Protocol", "ABC"]
+
+[[confusable_pairs]]
+name = "dataclass vs NamedTuple vs TypedDict"
+owner = "structured-data-shapes"
+tokens = ["@dataclass", "NamedTuple", "TypedDict"]
+
+[[confusable_pairs]]
+name = "bound vs unbound methods"
+owner = "bound-and-unbound-methods"
+tokens = ["bound method", "Class.method"]
+
+[[confusable_pairs]]
+name = "yield vs return"
+owner = "generators"
+tokens = ["yield", "return"]
+
+[[confusable_pairs]]
+name = "shallow vs deep copy"
+owner = "copying-collections"
+tokens = ["copy(", "deepcopy("]
+
+[[confusable_pairs]]
+name = "sync vs async functions"
+owner = "async-await"
+tokens = ["async def", "def "]
+
+[broad_surface_tours]
+
+[broad_surface_tours.special-methods]
+required_tokens = [
+ "__init__", "__repr__", "__str__", "__eq__", "__hash__", "__lt__",
+ "__len__", "__iter__", "__contains__", "__getitem__", "__setitem__",
+ "__call__", "__enter__", "__exit__", "__bool__",
+]
+
+[broad_surface_tours.operators]
+required_tokens = ["+", "==", " is ", " in ", "and", "or", "&", ":="]
+
+[broad_surface_tours.type-hints]
+required_tokens = ["list[", " | ", "Optional", "TypeAlias"]
+
+[broad_surface_tours.testing]
+required_tokens = ["TestCase", "assertEqual", "assertRaises", "setUp"]
+
+[broad_surface_tours.async-await]
+required_tokens = ["async def", "await", "asyncio.run", "asyncio.gather", "async for", "async with"]
+
+[broad_surface_tours.packages]
+required_tokens = ["__init__.py", "from .", "__all__"]
+
+[broad_surface_tours.regular-expressions]
+required_tokens = ["re.match", "re.search", "re.findall", "re.compile", "re.IGNORECASE", "re.sub"]
+
+[broad_surface_tours.literals]
+required_tokens = ["0x", "0b", "_", "f\"", "True", "None"]
+
+[[footguns]]
+name = "Mutable default class attribute"
+owner = "classes"
+broken_tokens = ["items = []"]
+fixed_tokens = ["self.items = []"]
+
+[[footguns]]
+name = "Mutable default function argument"
+owner = "functions"
+broken_tokens = ["items=[]", "append_broken"]
+fixed_tokens = ["items=None", "append_fixed"]
+
+[[footguns]]
+name = "Late-binding closure in a loop"
+owner = "closures"
+broken_tokens = ["lambda: i", "[2, 2, 2]"]
+fixed_tokens = ["lambda i=i", "[0, 1, 2]"]
+
+[[footguns]]
+name = "Integer identity caching"
+owner = "equality-and-identity"
+broken_tokens = ["small_a is small_b", "big_a is big_b"]
+fixed_tokens = ["big_a == big_b", "small-integer cache"]
+
+[[footguns]]
+name = "Shallow vs deep copy"
+owner = "copying-collections"
+broken_tokens = ["copy("]
+fixed_tokens = ["deepcopy("]
+
+[[footguns]]
+name = "Generator one-pass exhaustion"
+owner = "generators"
+broken_tokens = ["yield"]
+fixed_tokens = ["list("]
+
+[[footguns]]
+name = "Dictionary mutation during iteration"
+owner = "dicts"
+broken_tokens = ["for ", "del "]
+fixed_tokens = ["list(", ".keys("]
+
+[[footguns]]
+name = "Floating-point equality"
+owner = "numbers"
+broken_tokens = ["0.1", "0.2"]
+fixed_tokens = ["isclose", "math"]
+
+[[footguns]]
+name = "bool as a subclass of int"
+owner = "booleans"
+broken_tokens = ["isinstance(True, int)", "True + True"]
+fixed_tokens = ["not isinstance(value, bool)", "is_strict_int"]
+
+[[footguns]]
+name = "Bare except swallowing KeyboardInterrupt"
+owner = "exceptions"
+broken_tokens = ["except Exception"]
+fixed_tokens = ["except ValueError"]
+
+[paired_pages]
+# Pages whose titles differ only by a suffix or modifier; at least one
+# member of each pair must demonstrate the relationship in a cell.
+pairs = [
+ ["iterators", "iterating-over-iterables"],
+ ["iterators", "iterator-vs-iterable"],
+ ["generators", "generator-expressions"],
+ ["comprehensions", "comprehension-patterns"],
+]
diff --git a/scripts/check_broad_surface_tours.py b/scripts/check_broad_surface_tours.py
new file mode 100755
index 0000000..11959af
--- /dev/null
+++ b/scripts/check_broad_surface_tours.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+"""Verify that each broad-surface tour page covers its required tokens.
+
+Reads `docs/quality-registries.toml`. A page may opt out of the strict
+check by adding `scope_first_pass = true` to its frontmatter, in which
+case it must instead carry `see_also` links pointing at focused
+neighbors that the registry expects to exist.
+"""
+from __future__ import annotations
+
+import re
+import sys
+import tomllib
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[1]
+EXAMPLES_DIR = ROOT / "src" / "example_sources"
+REGISTRY_PATH = ROOT / "docs" / "quality-registries.toml"
+
+
+FRONTMATTER_RE = re.compile(r"^\+\+\+\n(.*?)\n\+\+\+\n", re.DOTALL)
+
+
+def parse_frontmatter(text: str) -> dict:
+ match = FRONTMATTER_RE.match(text)
+ if not match:
+ return {}
+ return tomllib.loads(match.group(1))
+
+
+def main() -> int:
+ data = tomllib.loads(REGISTRY_PATH.read_text())
+ tours = data.get("broad_surface_tours", {})
+ errors: list[str] = []
+ for slug, spec in tours.items():
+ page = EXAMPLES_DIR / f"{slug}.md"
+ if not page.exists():
+ errors.append(f"{REGISTRY_PATH}: broad-tour page missing: {slug}.md")
+ continue
+ text = page.read_text()
+ frontmatter = parse_frontmatter(text)
+ required = spec.get("required_tokens", [])
+ missing = [token for token in required if token not in text]
+ if frontmatter.get("scope_first_pass"):
+ see_also = frontmatter.get("see_also") or []
+ if not see_also:
+ errors.append(
+ f"{page}: scope_first_pass=true requires see_also links to focused neighbors"
+ )
+ continue
+ if missing:
+ errors.append(
+ f"{page}: broad-tour {slug!r} missing tokens: {missing}; "
+ "either add cells covering them or set scope_first_pass=true with see_also"
+ )
+ if errors:
+ for error in errors:
+ print(error, file=sys.stderr)
+ return 1
+ print(f"Broad-surface tour coverage OK ({len(tours)} pages).")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/check_confusable_pairs.py b/scripts/check_confusable_pairs.py
new file mode 100755
index 0000000..0e4e7ec
--- /dev/null
+++ b/scripts/check_confusable_pairs.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+"""Verify that each confusable pair appears on its owning page.
+
+Reads `docs/quality-registries.toml` and fails if the owning page's source
+text is missing any token from the pair. Tokens are matched as substrings,
+so they should be specific enough to avoid false positives.
+"""
+from __future__ import annotations
+
+import sys
+import tomllib
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[1]
+EXAMPLES_DIR = ROOT / "src" / "example_sources"
+REGISTRY_PATH = ROOT / "docs" / "quality-registries.toml"
+
+
+def main() -> int:
+ data = tomllib.loads(REGISTRY_PATH.read_text())
+ pairs = data.get("confusable_pairs", [])
+ errors: list[str] = []
+ for entry in pairs:
+ name = entry["name"]
+ owner = entry["owner"]
+ tokens = entry["tokens"]
+ page = EXAMPLES_DIR / f"{owner}.md"
+ if not page.exists():
+ errors.append(f"{REGISTRY_PATH}: owner page missing for {name!r}: {owner}.md")
+ continue
+ text = page.read_text()
+ missing = [token for token in tokens if token not in text]
+ if missing:
+ errors.append(f"{page}: confusable pair {name!r} missing tokens: {missing}")
+ if errors:
+ for error in errors:
+ print(error, file=sys.stderr)
+ return 1
+ print(f"Confusable-pair coverage OK ({len(pairs)} pairs).")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/check_footgun_coverage.py b/scripts/check_footgun_coverage.py
new file mode 100755
index 0000000..6856ccb
--- /dev/null
+++ b/scripts/check_footgun_coverage.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+"""Verify each canonical Python footgun has a page that shows broken+fixed.
+
+Reads `docs/quality-registries.toml`. The owning page must contain at
+least one token from each of `broken_tokens` and `fixed_tokens`. Token
+matching is permissive on purpose: this gate checks that the lesson is
+present, not that it is phrased in any particular way.
+"""
+from __future__ import annotations
+
+import sys
+import tomllib
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[1]
+EXAMPLES_DIR = ROOT / "src" / "example_sources"
+REGISTRY_PATH = ROOT / "docs" / "quality-registries.toml"
+
+
+def main() -> int:
+ data = tomllib.loads(REGISTRY_PATH.read_text())
+ footguns = data.get("footguns", [])
+ errors: list[str] = []
+ for entry in footguns:
+ name = entry["name"]
+ owner = entry["owner"]
+ broken = entry.get("broken_tokens", [])
+ fixed = entry.get("fixed_tokens", [])
+ page = EXAMPLES_DIR / f"{owner}.md"
+ if not page.exists():
+ errors.append(f"{REGISTRY_PATH}: footgun owner page missing: {owner}.md")
+ continue
+ text = page.read_text()
+ if not any(token in text for token in broken):
+ errors.append(
+ f"{page}: footgun {name!r} missing broken-form token; "
+ f"expected one of {broken}"
+ )
+ if not any(token in text for token in fixed):
+ errors.append(
+ f"{page}: footgun {name!r} missing fixed-form token; "
+ f"expected one of {fixed}"
+ )
+ if errors:
+ for error in errors:
+ print(error, file=sys.stderr)
+ return 1
+ print(f"Footgun coverage OK ({len(footguns)} entries).")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/check_notes_supported.py b/scripts/check_notes_supported.py
new file mode 100755
index 0000000..e094aa7
--- /dev/null
+++ b/scripts/check_notes_supported.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python3
+"""Strict check that each :::note bullet is grounded in the page body.
+
+For every example page, extract the `:::note` block and compare each
+bullet's keywords against tokens drawn from the rest of the page. A
+bullet that has no overlap with any cell, prose paragraph, or program
+fragment fails the build.
+
+The intent is to catch notes that assert something the page never
+demonstrates without forcing every bullet to mirror cell wording. Fix a
+failure by adding a cell that grounds the claim, citing the term in
+prose, or softening the note.
+"""
+from __future__ import annotations
+
+import re
+import sys
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[1]
+EXAMPLES_DIR = ROOT / "src" / "example_sources"
+
+
+NOTE_RE = re.compile(r":::note\n(.*?)\n:::", re.DOTALL)
+WORD_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]{2,}")
+STOPWORDS = {
+ "the", "and", "but", "with", "from", "that", "this", "these", "those",
+ "into", "onto", "over", "than", "then", "they", "them", "their",
+ "when", "where", "what", "which", "while", "would", "could", "should",
+ "have", "has", "had", "for", "use", "uses", "using", "used",
+ "are", "was", "were", "been", "being", "not", "any", "all",
+ "one", "two", "more", "most", "less", "least", "some",
+ "yes", "instead", "without", "between", "among", "every",
+ "page", "example", "cell", "code", "value", "values",
+ "python", "object", "objects", "function", "functions",
+ "method", "methods", "type", "types",
+}
+
+
+def tokens(text: str) -> set[str]:
+ return {match.group(0).lower() for match in WORD_RE.finditer(text)} - STOPWORDS
+
+
+def main() -> int:
+ errors: list[str] = []
+ for path in sorted(EXAMPLES_DIR.glob("*.md")):
+ text = path.read_text()
+ notes_match = NOTE_RE.search(text)
+ if not notes_match:
+ continue
+ body_outside_notes = text[: notes_match.start()] + text[notes_match.end():]
+ body_tokens = tokens(body_outside_notes)
+ for raw_line in notes_match.group(1).splitlines():
+ line = raw_line.strip()
+ if not line.startswith("- "):
+ continue
+ bullet = line[2:]
+ bullet_tokens = tokens(bullet)
+ if not bullet_tokens:
+ continue
+ if not bullet_tokens & body_tokens:
+ errors.append(
+ f"{path}: note bullet has no keyword overlap with the rest of the page: {bullet!r}"
+ )
+ if errors:
+ for error in errors:
+ print(error, file=sys.stderr)
+ return 1
+ print("Notes-supported check OK.")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/check_registry_integrity.py b/scripts/check_registry_integrity.py
new file mode 100755
index 0000000..9dffbf9
--- /dev/null
+++ b/scripts/check_registry_integrity.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python3
+"""Validate that the quality registries reference real example slugs.
+
+A typo in `docs/quality-registries.toml` would otherwise surface as a
+confusing "page missing" error from one of the coverage checks. This
+script runs first and fails fast with a clear message instead.
+"""
+from __future__ import annotations
+
+import sys
+import tomllib
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[1]
+EXAMPLES_DIR = ROOT / "src" / "example_sources"
+REGISTRY_PATH = ROOT / "docs" / "quality-registries.toml"
+MANIFEST_PATH = EXAMPLES_DIR / "manifest.toml"
+
+
+def main() -> int:
+ registries = tomllib.loads(REGISTRY_PATH.read_text())
+ manifest = tomllib.loads(MANIFEST_PATH.read_text())
+ known: set[str] = set(manifest.get("order", []))
+ errors: list[str] = []
+
+ for entry in registries.get("confusable_pairs", []):
+ owner = entry.get("owner")
+ if owner not in known:
+ errors.append(
+ f"confusable_pairs: {entry.get('name')!r} has unknown owner {owner!r}"
+ )
+ if not entry.get("tokens"):
+ errors.append(
+ f"confusable_pairs: {entry.get('name')!r} has no tokens"
+ )
+
+ for slug, spec in registries.get("broad_surface_tours", {}).items():
+ if slug not in known:
+ errors.append(f"broad_surface_tours: unknown slug {slug!r}")
+ if not spec.get("required_tokens"):
+ errors.append(f"broad_surface_tours: {slug!r} has no required_tokens")
+
+ for entry in registries.get("footguns", []):
+ owner = entry.get("owner")
+ if owner not in known:
+ errors.append(
+ f"footguns: {entry.get('name')!r} has unknown owner {owner!r}"
+ )
+ if not entry.get("broken_tokens"):
+ errors.append(f"footguns: {entry.get('name')!r} has no broken_tokens")
+ if not entry.get("fixed_tokens"):
+ errors.append(f"footguns: {entry.get('name')!r} has no fixed_tokens")
+
+ for pair in registries.get("paired_pages", {}).get("pairs", []):
+ for slug in pair:
+ if slug not in known:
+ errors.append(f"paired_pages: unknown slug {slug!r} in {pair}")
+
+ seen: set[tuple[str, str]] = set()
+ for entry in registries.get("confusable_pairs", []):
+ key = (entry.get("owner"), entry.get("name"))
+ if key in seen:
+ errors.append(f"confusable_pairs: duplicate entry {key}")
+ seen.add(key)
+
+ if errors:
+ for error in errors:
+ print(error, file=sys.stderr)
+ return 1
+ total = (
+ len(registries.get("confusable_pairs", []))
+ + len(registries.get("broad_surface_tours", {}))
+ + len(registries.get("footguns", []))
+ )
+ print(f"Registry integrity OK ({total} entries).")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/scripts/format_examples.py b/scripts/format_examples.py
index 03484da..494f236 100755
--- a/scripts/format_examples.py
+++ b/scripts/format_examples.py
@@ -9,7 +9,7 @@
ROOT = Path(__file__).resolve().parents[1]
SOURCE_DIR = ROOT / "src" / "example_sources"
-FRONTMATTER_ORDER = ["slug", "title", "section", "summary", "doc_path", "see_also", "min_python", "version_sensitive", "version_notes"]
+FRONTMATTER_ORDER = ["slug", "title", "section", "summary", "doc_path", "scope_first_pass", "see_also", "min_python", "version_sensitive", "version_notes"]
def toml_value(value: Any) -> str:
diff --git a/src/asset_manifest.py b/src/asset_manifest.py
index a69bd51..7764327 100644
--- a/src/asset_manifest.py
+++ b/src/asset_manifest.py
@@ -1,3 +1,3 @@
# Generated by scripts/fingerprint_assets.py. Do not edit by hand.
ASSET_PATHS = {'SITE_CSS': '/site.5f6f7da7c305.css', 'SYNTAX_JS': '/syntax-highlight.3b6c7f730d46.js', 'EDITOR_JS': '/editor.dd81f5171b14.js'}
-HTML_CACHE_VERSION = '79be58a175e3'
+HTML_CACHE_VERSION = '781554af9745'
diff --git a/src/example_sources/abstract-base-classes.md b/src/example_sources/abstract-base-classes.md
new file mode 100644
index 0000000..368dd0f
--- /dev/null
+++ b/src/example_sources/abstract-base-classes.md
@@ -0,0 +1,179 @@
++++
+slug = "abstract-base-classes"
+title = "Abstract Base Classes"
+section = "Classes"
+summary = "ABC and abstractmethod enforce that subclasses implement required methods."
+doc_path = "/library/abc.html"
+see_also = [
+ "protocols",
+ "inheritance-and-super",
+ "classes",
+]
++++
+
+`ABC` and `@abstractmethod` describe an interface that subclasses must implement. The base class refuses to instantiate until a concrete subclass provides every abstract method, which catches "I forgot to implement this" at construction time rather than at the first method call.
+
+ABCs are different from `Protocol`. An ABC is nominal: a class participates in the contract by inheriting from it. A `Protocol` is structural: any class with the right methods qualifies, no inheritance required. Reach for an ABC when you want shared implementation in the base class or you want `isinstance()` to mean "explicitly opted in"; reach for a `Protocol` when you only care about behavior at the API boundary.
+
+The cost is a small amount of ceremony at the type level. The benefit is that a half-implemented subclass cannot be created by accident.
+
+:::program
+```python
+from abc import ABC, abstractmethod
+from typing import Protocol
+
+class Shape(ABC):
+ @abstractmethod
+ def area(self) -> float:
+ ...
+
+ def describe(self) -> str:
+ return f"shape with area {self.area()}"
+
+try:
+ Shape()
+except TypeError as error:
+ print(error)
+
+class Square(Shape):
+ def __init__(self, side):
+ self.side = side
+
+ def area(self):
+ return self.side ** 2
+
+print(Square(3).area())
+print(Square(3).describe())
+
+class Incomplete(Shape):
+ pass
+
+try:
+ Incomplete()
+except TypeError as error:
+ print(error)
+
+class HasArea(Protocol):
+ def area(self) -> float:
+ ...
+
+class Triangle:
+ def __init__(self, base, height):
+ self.base = base
+ self.height = height
+
+ def area(self):
+ return 0.5 * self.base * self.height
+
+def total_area(shapes: list[HasArea]) -> float:
+ return sum(shape.area() for shape in shapes)
+
+print(total_area([Square(3), Triangle(4, 3)]))
+print(isinstance(Triangle(4, 3), Shape))
+print(isinstance(Square(3), Shape))
+```
+:::
+
+:::cell
+`ABC` plus `@abstractmethod` declares the contract. Trying to construct the base class itself fails because at least one method has no implementation. A concrete `describe()` lives alongside the abstract `area()` so subclasses inherit shared behavior for free.
+
+```python
+from abc import ABC, abstractmethod
+
+class Shape(ABC):
+ @abstractmethod
+ def area(self) -> float:
+ ...
+
+ def describe(self) -> str:
+ return f"shape with area {self.area()}"
+
+try:
+ Shape()
+except TypeError as error:
+ print(error)
+```
+
+```output
+Can't instantiate abstract class Shape without an implementation for abstract method 'area'
+```
+:::
+
+:::cell
+A subclass that implements every abstract method is concrete and can be instantiated. It also inherits the non-abstract methods from the base class.
+
+```python
+class Square(Shape):
+ def __init__(self, side):
+ self.side = side
+
+ def area(self):
+ return self.side ** 2
+
+print(Square(3).area())
+print(Square(3).describe())
+```
+
+```output
+9
+shape with area 9
+```
+:::
+
+:::cell
+A subclass that forgets to implement an abstract method also cannot be instantiated — that is the value the ABC adds. The error fires at construction, not when something later tries to call the missing method.
+
+```python
+class Incomplete(Shape):
+ pass
+
+try:
+ Incomplete()
+except TypeError as error:
+ print(error)
+```
+
+```output
+Can't instantiate abstract class Incomplete without an implementation for abstract method 'area'
+```
+:::
+
+:::cell
+Contrast with `Protocol`. A `HasArea` protocol accepts any class with an `area()` method, no inheritance required. `Triangle` does not inherit from `Shape`, so it satisfies the protocol but fails `isinstance(_, Shape)`. `Square` satisfies both because it explicitly inherited from the ABC.
+
+```python
+from typing import Protocol
+
+class HasArea(Protocol):
+ def area(self) -> float:
+ ...
+
+class Triangle:
+ def __init__(self, base, height):
+ self.base = base
+ self.height = height
+
+ def area(self):
+ return 0.5 * self.base * self.height
+
+def total_area(shapes: list[HasArea]) -> float:
+ return sum(shape.area() for shape in shapes)
+
+print(total_area([Square(3), Triangle(4, 3)]))
+print(isinstance(Triangle(4, 3), Shape))
+print(isinstance(Square(3), Shape))
+```
+
+```output
+15.0
+False
+True
+```
+:::
+
+:::note
+- `ABC` plus `@abstractmethod` blocks instantiation until every abstract method has an implementation.
+- ABCs are nominal — subclasses opt in by inheriting; `isinstance()` reflects that opt-in.
+- Protocols are structural — any class with the right shape qualifies, regardless of inheritance.
+- Prefer an ABC when shared implementation or explicit opt-in matters; prefer a Protocol when only behavior at the API boundary matters.
+:::
diff --git a/src/example_sources/async-await.md b/src/example_sources/async-await.md
index 7c6853a..6a8179f 100644
--- a/src/example_sources/async-await.md
+++ b/src/example_sources/async-await.md
@@ -4,6 +4,11 @@ title = "Async Await"
section = "Async"
summary = "async def creates coroutines, and await pauses until awaitable work completes."
doc_path = "/library/asyncio-task.html"
+see_also = [
+ "async-iteration-and-context",
+ "functions",
+ "context-managers",
+]
+++
`async def` creates a coroutine function. Calling it creates a coroutine object; the body runs when an event loop awaits or schedules it.
@@ -29,6 +34,30 @@ async def main():
print(titles)
asyncio.run(main())
+
+
+class Session:
+ async def __aenter__(self):
+ print("open")
+ return self
+
+ async def __aexit__(self, *_):
+ print("close")
+ return False
+
+
+async def stream():
+ for slug in ["json", "datetime"]:
+ await asyncio.sleep(0)
+ yield slug
+
+
+async def driver():
+ async with Session():
+ async for slug in stream():
+ print(slug)
+
+asyncio.run(driver())
```
:::
@@ -84,6 +113,42 @@ asyncio.run(main())
```
:::
+:::cell
+`async with` and `async for` are the asynchronous forms of context managers and iteration. A class implements `__aenter__`/`__aexit__` to act as an async context manager; an `async def` function with `yield` becomes an async generator. The dedicated [async iteration and context](/iteration/async-iteration-and-context) page explains the protocols in depth.
+
+```python
+class Session:
+ async def __aenter__(self):
+ print("open")
+ return self
+
+ async def __aexit__(self, *_):
+ print("close")
+ return False
+
+
+async def stream():
+ for slug in ["json", "datetime"]:
+ await asyncio.sleep(0)
+ yield slug
+
+
+async def driver():
+ async with Session():
+ async for slug in stream():
+ print(slug)
+
+asyncio.run(driver())
+```
+
+```output
+open
+json
+datetime
+close
+```
+:::
+
:::note
- Calling an async function creates a coroutine object.
- `await` yields control until an awaitable completes.
diff --git a/src/example_sources/attribute-access.md b/src/example_sources/attribute-access.md
index e8d35c6..7fa4a98 100644
--- a/src/example_sources/attribute-access.md
+++ b/src/example_sources/attribute-access.md
@@ -8,6 +8,7 @@ see_also = [
"properties",
"descriptors",
"special-methods",
+ "bound-and-unbound-methods",
]
+++
diff --git a/src/example_sources/booleans.md b/src/example_sources/booleans.md
index debb2fe..03861a0 100644
--- a/src/example_sources/booleans.md
+++ b/src/example_sources/booleans.md
@@ -23,6 +23,16 @@ print(not has_permission)
name = "Ada"
print(name == "Ada" and len(name) > 0)
+
+print(isinstance(True, int))
+print(True + True)
+print(sum([True, True, False, True]))
+
+def is_strict_int(value):
+ return isinstance(value, int) and not isinstance(value, bool)
+
+print(is_strict_int(True))
+print(is_strict_int(1))
```
:::
@@ -60,8 +70,33 @@ True
```
:::
+:::cell
+`bool` is a subclass of `int`, which is occasionally a footgun. `True` behaves as `1` and `False` as `0` in arithmetic, and `isinstance(True, int)` is `True`. When a function must reject booleans, exclude them explicitly with `isinstance(value, int) and not isinstance(value, bool)`.
+
+```python
+print(isinstance(True, int))
+print(True + True)
+print(sum([True, True, False, True]))
+
+def is_strict_int(value):
+ return isinstance(value, int) and not isinstance(value, bool)
+
+print(is_strict_int(True))
+print(is_strict_int(1))
+```
+
+```output
+True
+2
+3
+False
+True
+```
+:::
+
:::note
- Boolean constants are `True` and `False`, with capital letters.
- `and` and `or` short-circuit: Python does not evaluate the right side if the left side already determines the result.
- Prefer truthiness for containers and explicit comparisons when the exact boolean condition matters.
+- `bool` subclasses `int`; `isinstance(True, int)` is `True`. Exclude booleans explicitly when only "real" integers should pass.
:::
diff --git a/src/example_sources/bound-and-unbound-methods.md b/src/example_sources/bound-and-unbound-methods.md
new file mode 100644
index 0000000..01ea077
--- /dev/null
+++ b/src/example_sources/bound-and-unbound-methods.md
@@ -0,0 +1,148 @@
++++
+slug = "bound-and-unbound-methods"
+title = "Bound and Unbound Methods"
+section = "Data Model"
+summary = "instance.method binds self automatically; Class.method is a plain function."
+doc_path = "/reference/datamodel.html#instance-methods"
+see_also = [
+ "classes",
+ "attribute-access",
+ "descriptors",
+ "callable-objects",
+]
++++
+
+When you write `instance.method`, Python returns a bound method — a callable that already remembers which instance to pass as `self`. When you write `Class.method`, you get the underlying function back, and calling it requires passing an instance yourself.
+
+That distinction is why methods can be stored in collections, passed as callbacks, and called later without losing track of the object they belong to. Each bound method carries its own `__self__`, so two callables produced from two different instances stay independent even when their underlying function is the same.
+
+The mechanism is the descriptor protocol: a function attached to a class implements `__get__`, and that hook turns attribute access on an instance into a bound method. The page does not need that detail to use methods, but it explains what is happening underneath.
+
+:::program
+```python
+class Counter:
+ def __init__(self, start=0):
+ self.value = start
+
+ def increment(self):
+ self.value += 1
+ return self.value
+
+bound_counter = Counter(10)
+m = bound_counter.increment
+print(m.__self__ is bound_counter)
+print(m())
+print(m())
+
+unbound_counter = Counter(0)
+unbound = Counter.increment
+print(type(unbound).__name__)
+print(unbound(unbound_counter))
+print(unbound(unbound_counter))
+
+handlers = []
+for _ in range(2):
+ handlers.append(Counter().increment)
+
+print(handlers[0]())
+print(handlers[0]())
+print(handlers[1]())
+
+descriptor_counter = Counter(0)
+func = Counter.__dict__["increment"]
+print(type(func).__name__)
+rebound = func.__get__(descriptor_counter, Counter)
+print(type(rebound).__name__)
+print(rebound.__self__ is descriptor_counter)
+```
+:::
+
+:::cell
+`instance.method` returns a bound method. The method already remembers the instance through `__self__`, so calling it does not require passing `self` again.
+
+```python
+class Counter:
+ def __init__(self, start=0):
+ self.value = start
+
+ def increment(self):
+ self.value += 1
+ return self.value
+
+bound_counter = Counter(10)
+m = bound_counter.increment
+print(m.__self__ is bound_counter)
+print(m())
+print(m())
+```
+
+```output
+True
+11
+12
+```
+:::
+
+:::cell
+`Class.method` returns the underlying function — there is no `self` attached. Calling it requires passing the instance as the first argument explicitly. Using a fresh counter here makes the output independent of the previous cell.
+
+```python
+unbound_counter = Counter(0)
+unbound = Counter.increment
+print(type(unbound).__name__)
+print(unbound(unbound_counter))
+print(unbound(unbound_counter))
+```
+
+```output
+function
+1
+2
+```
+:::
+
+:::cell
+Bound methods are first-class values. They can be stored in lists, passed to other functions, and called later. Each bound method carries its own `__self__`, so two methods produced from two different instances stay independent.
+
+```python
+handlers = []
+for _ in range(2):
+ handlers.append(Counter().increment)
+
+print(handlers[0]())
+print(handlers[0]())
+print(handlers[1]())
+```
+
+```output
+1
+2
+1
+```
+:::
+
+:::cell
+The binding is the descriptor protocol at work. The function lives on the class as a plain function; instance attribute access invokes `__get__`, which returns a bound method that knows the instance.
+
+```python
+descriptor_counter = Counter(0)
+func = Counter.__dict__["increment"]
+print(type(func).__name__)
+rebound = func.__get__(descriptor_counter, Counter)
+print(type(rebound).__name__)
+print(rebound.__self__ is descriptor_counter)
+```
+
+```output
+function
+method
+True
+```
+:::
+
+:::note
+- `instance.method` produces a bound method whose `__self__` is the instance.
+- `Class.method` produces the plain function and requires you to pass the instance.
+- Each bound method is its own object; storing one captures its instance.
+- The binding is implemented by the descriptor protocol on the function object.
+:::
diff --git a/src/example_sources/callable-objects.md b/src/example_sources/callable-objects.md
index 4c93c20..419de73 100644
--- a/src/example_sources/callable-objects.md
+++ b/src/example_sources/callable-objects.md
@@ -8,6 +8,7 @@ see_also = [
"functions",
"closures",
"callable-types",
+ "bound-and-unbound-methods",
]
+++
diff --git a/src/example_sources/classes.md b/src/example_sources/classes.md
index 3c0b9b3..05f24b4 100644
--- a/src/example_sources/classes.md
+++ b/src/example_sources/classes.md
@@ -4,6 +4,12 @@ title = "Classes"
section = "Classes"
summary = "Classes bundle data and behavior into new object types."
doc_path = "/tutorial/classes.html"
+see_also = [
+ "inheritance-and-super",
+ "classmethods-and-staticmethods",
+ "bound-and-unbound-methods",
+ "dataclasses",
+]
+++
Classes define new object types by bundling data with behavior. They are useful when several values and operations belong together and should travel as one object.
@@ -15,6 +21,8 @@ The alternative is often a dictionary plus separate functions. That is fine for
:::program
```python
class Counter:
+ step = 1
+
def __init__(self, start=0):
self.value = start
@@ -29,6 +37,32 @@ print(first.value)
print(second.value)
print(first.increment())
print(second.increment(5))
+print(first.step)
+Counter.step = 5
+print(second.step)
+
+class Cart:
+ items = []
+
+ def add(self, item):
+ self.items.append(item)
+
+shared_a = Cart()
+shared_b = Cart()
+shared_a.add("apple")
+print(shared_b.items)
+
+class FixedCart:
+ def __init__(self):
+ self.items = []
+
+ def add(self, item):
+ self.items.append(item)
+
+own_a = FixedCart()
+own_b = FixedCart()
+own_a.add("apple")
+print(own_b.items)
```
:::
@@ -76,9 +110,67 @@ print(second.increment(5))
```
:::
+:::cell
+A name defined directly on the class body is a class attribute, shared by every instance. Reading falls back to the class when the instance has no attribute of that name; assigning to the class itself changes the value for every instance at once.
+
+```python
+class Counter:
+ step = 1
+
+ def __init__(self, start=0):
+ self.value = start
+
+first = Counter()
+second = Counter()
+print(first.step)
+Counter.step = 5
+print(second.step)
+```
+
+```output
+1
+5
+```
+:::
+
+:::cell
+A mutable class attribute is shared mutable state — the classic footgun. Define per-instance containers in `__init__` so each object owns its own copy.
+
+```python
+class Cart:
+ items = []
+
+ def add(self, item):
+ self.items.append(item)
+
+shared_a = Cart()
+shared_b = Cart()
+shared_a.add("apple")
+print(shared_b.items)
+
+class FixedCart:
+ def __init__(self):
+ self.items = []
+
+ def add(self, item):
+ self.items.append(item)
+
+own_a = FixedCart()
+own_b = FixedCart()
+own_a.add("apple")
+print(own_b.items)
+```
+
+```output
+['apple']
+[]
+```
+:::
+
:::note
- `self` is the instance the method is operating on.
- `__init__` initializes each new object.
+- Class attributes are shared across instances; instance attributes belong to one object.
+- Put mutable defaults in `__init__`, not on the class body.
- Use classes when behavior belongs with state; use dictionaries for looser structured data.
-- Instance attributes belong to one object, not to the class as a whole.
:::
diff --git a/src/example_sources/classmethods-and-staticmethods.md b/src/example_sources/classmethods-and-staticmethods.md
new file mode 100644
index 0000000..b80c4cf
--- /dev/null
+++ b/src/example_sources/classmethods-and-staticmethods.md
@@ -0,0 +1,165 @@
++++
+slug = "classmethods-and-staticmethods"
+title = "Classmethods and Staticmethods"
+section = "Classes"
+summary = "Three method shapes: instance, class, and static — each receives a different first argument."
+doc_path = "/library/functions.html#classmethod"
+see_also = [
+ "classes",
+ "decorators",
+ "inheritance-and-super",
+]
++++
+
+A regular method receives the instance as `self`. `@classmethod` makes a method receive the class as `cls` instead, which is the standard shape for alternate constructors. `@staticmethod` removes the implicit first argument entirely, leaving a plain function attached to the class for namespacing.
+
+The pressure that justifies the decorators is name organization. `Date.from_string("2026-05-09")` reads better than a free-floating `parse_date` function, and `Date.is_leap_year(2024)` keeps the helper next to the class it belongs to even when the helper does not need any class state.
+
+Pick instance methods when the work depends on instance state, classmethods when an alternate constructor or class-level operation is the right shape, and staticmethods when the function only happens to live near a class.
+
+:::program
+```python
+class Date:
+ def __init__(self, year, month, day):
+ self.year = year
+ self.month = month
+ self.day = day
+
+ def display(self):
+ return f"{self.year}-{self.month:02d}-{self.day:02d}"
+
+ @classmethod
+ def from_string(cls, text):
+ year, month, day = (int(part) for part in text.split("-"))
+ return cls(year, month, day)
+
+ @staticmethod
+ def is_leap_year(year):
+ return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
+
+today = Date(2026, 5, 9)
+print(today.display())
+
+later = Date.from_string("2026-12-31")
+print(later.display())
+
+print(Date.is_leap_year(2024))
+print(Date.is_leap_year(2025))
+
+class Demo:
+ def instance_method(self):
+ return type(self).__name__
+
+ @classmethod
+ def class_method(cls):
+ return cls.__name__
+
+ @staticmethod
+ def static_method():
+ return "no receiver"
+
+print(Demo().instance_method())
+print(Demo.class_method())
+print(Demo.static_method())
+```
+:::
+
+:::cell
+An instance method receives the instance as `self` and reads its state. This is the default and the right shape when the work depends on a particular object's data.
+
+```python
+class Date:
+ def __init__(self, year, month, day):
+ self.year = year
+ self.month = month
+ self.day = day
+
+ def display(self):
+ return f"{self.year}-{self.month:02d}-{self.day:02d}"
+
+today = Date(2026, 5, 9)
+print(today.display())
+```
+
+```output
+2026-05-09
+```
+:::
+
+:::cell
+`@classmethod` makes the method receive the class itself as `cls`. The canonical use is an alternate constructor that parses some other input format and calls `cls(...)`. Because `cls` is the actual class, subclasses calling the same method get an instance of their own type.
+
+```python
+class Date:
+ def __init__(self, year, month, day):
+ self.year = year
+ self.month = month
+ self.day = day
+
+ @classmethod
+ def from_string(cls, text):
+ year, month, day = (int(part) for part in text.split("-"))
+ return cls(year, month, day)
+
+later = Date.from_string("2026-12-31")
+print(later.year, later.month, later.day)
+```
+
+```output
+2026 12 31
+```
+:::
+
+:::cell
+`@staticmethod` strips the implicit first argument. The function lives on the class for namespacing — like `Date.is_leap_year(2024)` — but does not touch any instance or class state.
+
+```python
+class Date:
+ @staticmethod
+ def is_leap_year(year):
+ return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
+
+print(Date.is_leap_year(2024))
+print(Date.is_leap_year(2025))
+```
+
+```output
+True
+False
+```
+:::
+
+:::cell
+Side by side: instance methods receive the instance, classmethods receive the class, staticmethods receive nothing. Classmethods and staticmethods can be called on either the class or an instance.
+
+```python
+class Demo:
+ def instance_method(self):
+ return type(self).__name__
+
+ @classmethod
+ def class_method(cls):
+ return cls.__name__
+
+ @staticmethod
+ def static_method():
+ return "no receiver"
+
+print(Demo().instance_method())
+print(Demo.class_method())
+print(Demo.static_method())
+```
+
+```output
+Demo
+Demo
+no receiver
+```
+:::
+
+:::note
+- Instance methods need an instance; classmethods and staticmethods can be called on the class.
+- Use `@classmethod` for alternate constructors and class-level operations that respect subclassing.
+- Use `@staticmethod` only when a function is truly independent of instance and class state but still belongs in the class's namespace.
+- A free function is often the right answer when neither decorator applies.
+:::
diff --git a/src/example_sources/closures.md b/src/example_sources/closures.md
index 587c74e..dfcb0ea 100644
--- a/src/example_sources/closures.md
+++ b/src/example_sources/closures.md
@@ -24,6 +24,16 @@ print(double(5))
triple = make_multiplier(3)
print(triple(5))
+
+late = []
+for i in range(3):
+ late.append(lambda: i)
+print([f() for f in late])
+
+bound = []
+for i in range(3):
+ bound.append(lambda i=i: i)
+print([f() for f in bound])
```
:::
@@ -58,8 +68,30 @@ print(triple(5))
```
:::
+:::cell
+Closures bind names, not values. Lambdas defined in a loop all reference the same loop variable, so calling them later sees its final value. Capture the value at definition time by binding it as a default argument — `lambda i=i: i` — so each closure remembers its own `i`.
+
+```python
+late = []
+for i in range(3):
+ late.append(lambda: i)
+print([f() for f in late])
+
+bound = []
+for i in range(3):
+ bound.append(lambda i=i: i)
+print([f() for f in bound])
+```
+
+```output
+[2, 2, 2]
+[0, 1, 2]
+```
+:::
+
:::note
- A closure keeps access to names from the scope where the inner function was created.
- Each call to the outer function can create a separate remembered environment.
- Closures are useful for callbacks, small factories, and decorators.
+- Closures bind names, not values; capture loop variables with `lambda x=x: ...` to freeze them at definition time.
:::
diff --git a/src/example_sources/dataclasses.md b/src/example_sources/dataclasses.md
index 9782066..9b53304 100644
--- a/src/example_sources/dataclasses.md
+++ b/src/example_sources/dataclasses.md
@@ -4,6 +4,11 @@ title = "Dataclasses"
section = "Classes"
summary = "dataclass generates common class methods for data containers."
doc_path = "/library/dataclasses.html"
+see_also = [
+ "structured-data-shapes",
+ "classes",
+ "type-hints",
+]
+++
`dataclass` is a standard-library decorator for classes that mainly store data. It generates methods such as `__init__` and `__repr__` from type-annotated fields.
diff --git a/src/example_sources/decorators.md b/src/example_sources/decorators.md
index b175e12..4b19a65 100644
--- a/src/example_sources/decorators.md
+++ b/src/example_sources/decorators.md
@@ -8,6 +8,7 @@ see_also = [
"closures",
"functions",
"callable-types",
+ "classmethods-and-staticmethods",
]
+++
diff --git a/src/example_sources/descriptors.md b/src/example_sources/descriptors.md
index 7b49bf3..de17d0d 100644
--- a/src/example_sources/descriptors.md
+++ b/src/example_sources/descriptors.md
@@ -4,6 +4,11 @@ title = "Descriptors"
section = "Data Model"
summary = "Descriptors customize attribute access through __get__, __set__, or __delete__."
doc_path = "/howto/descriptor.html"
+see_also = [
+ "attribute-access",
+ "properties",
+ "bound-and-unbound-methods",
+]
+++
Descriptors customize attribute access through __get__, __set__, or __delete__. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.
diff --git a/src/example_sources/dicts.md b/src/example_sources/dicts.md
index 47062dc..4ca57ed 100644
--- a/src/example_sources/dicts.md
+++ b/src/example_sources/dicts.md
@@ -25,6 +25,12 @@ print(scores.get("Guido", 0))
for name, score in scores.items():
print(f"{name}: {score}")
+
+inventory = {"apple": 0, "pear": 3, "plum": 0}
+for name in list(inventory.keys()):
+ if inventory[name] == 0:
+ del inventory[name]
+print(inventory)
```
:::
@@ -73,8 +79,25 @@ Grace: 9
```
:::
+:::cell
+Mutating a dictionary while iterating it raises `RuntimeError`. Snapshot the keys with `list(d.keys())` (or build a list of changes and apply them after the loop) so the iteration sees a stable view.
+
+```python
+inventory = {"apple": 0, "pear": 3, "plum": 0}
+for name in list(inventory.keys()):
+ if inventory[name] == 0:
+ del inventory[name]
+print(inventory)
+```
+
+```output
+{'pear': 3}
+```
+:::
+
:::note
- Dictionaries preserve insertion order in modern Python.
- Use `get()` when a missing key has a reasonable default.
- Use direct indexing when a missing key should be treated as an error.
+- Snapshot keys with `list(d.keys())` before deleting items in a loop; mutating during iteration raises `RuntimeError`.
:::
diff --git a/src/example_sources/equality-and-identity.md b/src/example_sources/equality-and-identity.md
index aded85f..515839a 100644
--- a/src/example_sources/equality-and-identity.md
+++ b/src/example_sources/equality-and-identity.md
@@ -26,6 +26,15 @@ print(same is left)
value = None
print(value is None)
+
+small_a = 100
+small_b = 100
+print(small_a is small_b)
+
+big_a = int("1000")
+big_b = int("1000")
+print(big_a is big_b)
+print(big_a == big_b)
```
:::
@@ -74,8 +83,30 @@ True
```
:::
+:::cell
+`is` for integers is unreliable because CPython caches small integers (roughly `-5` to `256`) but not larger ones. Two equal large integers can be different objects. Use `==` for value comparisons; reserve `is` for singletons.
+
+```python
+small_a = 100
+small_b = 100
+print(small_a is small_b)
+
+big_a = int("1000")
+big_b = int("1000")
+print(big_a is big_b)
+print(big_a == big_b)
+```
+
+```output
+True
+False
+True
+```
+:::
+
:::note
- Use `==` for ordinary value comparisons.
- Use `is` primarily for identity checks against singletons such as `None`.
- Equal mutable containers can still be independent objects.
+- Never use `is` to compare numbers; CPython's small-integer cache makes the result an implementation detail.
:::
diff --git a/src/example_sources/exceptions.md b/src/example_sources/exceptions.md
index 0e2ac4d..f0a5264 100644
--- a/src/example_sources/exceptions.md
+++ b/src/example_sources/exceptions.md
@@ -28,6 +28,22 @@ for text in ["42", "python"]:
print(f"{text}: {number}")
finally:
print(f"checked {text}")
+
+
+def safe_parse_broken(text):
+ try:
+ return int(text)
+ except Exception:
+ return None
+
+def safe_parse_fixed(text):
+ try:
+ return int(text)
+ except ValueError:
+ return None
+
+print(safe_parse_broken("42"))
+print(safe_parse_fixed("42"))
```
:::
@@ -76,8 +92,35 @@ checked python
```
:::
+:::cell
+Bare `except:` and broad `except Exception:` swallow far more than the failure you meant to handle, including `KeyboardInterrupt` (bare) and most programming bugs (broad). Catch the specific class — `ValueError` here — so unexpected failures still surface.
+
+```python
+def safe_parse_broken(text):
+ try:
+ return int(text)
+ except Exception:
+ return None
+
+def safe_parse_fixed(text):
+ try:
+ return int(text)
+ except ValueError:
+ return None
+
+print(safe_parse_broken("42"))
+print(safe_parse_fixed("42"))
+```
+
+```output
+42
+42
+```
+:::
+
:::note
- Catch the most specific exception you can.
- `else` is for success code that should run only if the `try` block did not fail.
- `finally` runs whether the operation succeeded or failed.
+- Avoid bare `except:` and broad `except Exception:` — they hide bugs and absorb signals like `KeyboardInterrupt`.
:::
diff --git a/src/example_sources/functions.md b/src/example_sources/functions.md
index 9fe9de0..c66ea00 100644
--- a/src/example_sources/functions.md
+++ b/src/example_sources/functions.md
@@ -32,6 +32,24 @@ def log(message):
result = log("saved")
print(result)
+
+
+def append_broken(item, items=[]):
+ items.append(item)
+ return items
+
+print(append_broken("a"))
+print(append_broken("b"))
+
+
+def append_fixed(item, items=None):
+ if items is None:
+ items = []
+ items.append(item)
+ return items
+
+print(append_fixed("a"))
+print(append_fixed("b"))
```
:::
@@ -84,8 +102,39 @@ None
```
:::
+:::cell
+Mutable default arguments are evaluated once when the function is defined, not on each call. The same list is shared across calls, so successive calls see each other's mutations. Use `None` as the sentinel and create a fresh container inside the body.
+
+```python
+def append_broken(item, items=[]):
+ items.append(item)
+ return items
+
+print(append_broken("a"))
+print(append_broken("b"))
+
+
+def append_fixed(item, items=None):
+ if items is None:
+ items = []
+ items.append(item)
+ return items
+
+print(append_fixed("a"))
+print(append_fixed("b"))
+```
+
+```output
+['a']
+['a', 'b']
+['a']
+['b']
+```
+:::
+
:::note
- Use `return` for values the caller should receive.
- Defaults keep common calls concise.
- Keyword arguments make options readable at the call site.
+- Never use a mutable value as a default argument; use `None` and build the container inside the function body.
:::
diff --git a/src/example_sources/generators.md b/src/example_sources/generators.md
index 9408d7b..bc2f2ab 100644
--- a/src/example_sources/generators.md
+++ b/src/example_sources/generators.md
@@ -4,6 +4,11 @@ title = "Generators"
section = "Iteration"
summary = "yield creates an iterator that produces values on demand."
doc_path = "/tutorial/classes.html#generators"
+see_also = [
+ "iterators",
+ "iterator-vs-iterable",
+ "generator-expressions",
+]
+++
A generator function is a convenient way to write your own iterator. `yield` produces one value, pauses the function, and resumes when the next value is requested.
@@ -25,6 +30,37 @@ print(next(numbers))
for value in countdown(3):
print(value)
+
+def countdown_eager(n):
+ result = []
+ while n > 0:
+ result.append(n)
+ n -= 1
+ return result
+
+values = countdown_eager(3)
+print(values)
+print(values)
+
+stream = countdown(3)
+print(list(stream))
+print(list(stream))
+
+class Countdown:
+ def __init__(self, n):
+ self.n = n
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ if self.n <= 0:
+ raise StopIteration
+ value = self.n
+ self.n -= 1
+ return value
+
+print(list(Countdown(3)))
```
:::
@@ -63,9 +99,63 @@ for value in countdown(3):
```
:::
+:::cell
+`return` builds the entire result before handing it back; `yield` produces values on demand. The list keeps its values for repeated use, while the generator is exhausted after one pass.
+
+```python
+def countdown_eager(n):
+ result = []
+ while n > 0:
+ result.append(n)
+ n -= 1
+ return result
+
+values = countdown_eager(3)
+print(values)
+print(values)
+
+stream = countdown(3)
+print(list(stream))
+print(list(stream))
+```
+
+```output
+[3, 2, 1]
+[3, 2, 1]
+[3, 2, 1]
+[]
+```
+:::
+
+:::cell
+Every generator is an iterator. The same countdown written by hand needs `__iter__` and `__next__` and an explicit `StopIteration`. The generator function expresses the same protocol with one `yield`.
+
+```python
+class Countdown:
+ def __init__(self, n):
+ self.n = n
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ if self.n <= 0:
+ raise StopIteration
+ value = self.n
+ self.n -= 1
+ return value
+
+print(list(Countdown(3)))
+```
+
+```output
+[3, 2, 1]
+```
+:::
+
:::note
-- Generator functions are a concise way to create custom iterators.
-- Values are produced on demand.
+- Generator functions are a concise way to create custom iterators; every generator is an iterator.
+- `yield` defers work and streams values; `return` produces the whole result up front.
- A generator is consumed as you iterate over it.
- Prefer a list when you need to reuse stored results; prefer a generator when values can be streamed once.
:::
diff --git a/src/example_sources/inheritance-and-super.md b/src/example_sources/inheritance-and-super.md
index bdc1801..ce0954c 100644
--- a/src/example_sources/inheritance-and-super.md
+++ b/src/example_sources/inheritance-and-super.md
@@ -6,7 +6,8 @@ summary = "Inheritance reuses behavior, and super delegates to a parent implemen
doc_path = "/tutorial/classes.html#inheritance"
see_also = [
"classes",
- "properties",
+ "abstract-base-classes",
+ "classmethods-and-staticmethods",
"special-methods",
]
+++
diff --git a/src/example_sources/iterating-over-iterables.md b/src/example_sources/iterating-over-iterables.md
index 61cbfe6..541d11d 100644
--- a/src/example_sources/iterating-over-iterables.md
+++ b/src/example_sources/iterating-over-iterables.md
@@ -4,6 +4,11 @@ title = "Iterating over Iterables"
section = "Iteration"
summary = "for loops consume values from any iterable object."
doc_path = "/tutorial/controlflow.html#for-statements"
+see_also = [
+ "iterators",
+ "iterator-vs-iterable",
+ "for-loops",
+]
+++
Python's `for` statement consumes values from any iterable object: lists, strings, dictionaries, ranges, generators, files, and many standard-library helpers.
diff --git a/src/example_sources/iterator-vs-iterable.md b/src/example_sources/iterator-vs-iterable.md
new file mode 100644
index 0000000..66aed45
--- /dev/null
+++ b/src/example_sources/iterator-vs-iterable.md
@@ -0,0 +1,134 @@
++++
+slug = "iterator-vs-iterable"
+title = "Iterator vs Iterable"
+section = "Iteration"
+summary = "Iterables produce fresh iterators; iterators are one-pass."
+doc_path = "/glossary.html#term-iterable"
+see_also = [
+ "iterators",
+ "iterating-over-iterables",
+ "generators",
+]
++++
+
+An iterable can produce values when asked. An iterator is the object that remembers where the production currently is. The distinction matters because iterables can be traversed many times, while many iterators can be traversed only once.
+
+`iter(iterable)` returns a fresh iterator each call. `iter(iterator)` returns the iterator itself. That self-iteration property is how `for` loops can accept either kind, and it is also why a function that loops over its argument twice silently breaks when called with a generator instead of a list.
+
+The takeaway for API design: receive iterables when the caller may want a second pass, and materialize once at the boundary if you must.
+
+:::program
+```python
+names = ["Ada", "Grace"]
+
+print(list(names))
+print(list(names))
+
+stream = iter(names)
+print(list(stream))
+print(list(stream))
+
+first = iter(names)
+second = iter(names)
+print(first is second)
+print(iter(first) is first)
+
+def total_and_count(numbers):
+ total = sum(numbers)
+ count = sum(1 for _ in numbers)
+ return total, count
+
+def values():
+ yield from [10, 9, 8]
+
+print(total_and_count([10, 9, 8]))
+print(total_and_count(values()))
+
+def total_and_count_safe(numbers):
+ items = list(numbers)
+ return sum(items), len(items)
+
+print(total_and_count_safe(values()))
+```
+:::
+
+:::cell
+A list is iterable. Each `for` loop or `list()` call asks the list for a fresh iterator under the hood, so the same data can be traversed many times.
+
+```python
+names = ["Ada", "Grace"]
+print(list(names))
+print(list(names))
+```
+
+```output
+['Ada', 'Grace']
+['Ada', 'Grace']
+```
+:::
+
+:::cell
+An iterator is one-pass. Calling `iter()` returns a position-tracking object; once it has been exhausted, it stays exhausted.
+
+```python
+stream = iter(names)
+print(list(stream))
+print(list(stream))
+```
+
+```output
+['Ada', 'Grace']
+[]
+```
+:::
+
+:::cell
+Calling `iter()` on an iterable returns a brand-new iterator each time. Calling `iter()` on an iterator returns the same object — that is the rule that lets a `for` loop accept either kind.
+
+```python
+first = iter(names)
+second = iter(names)
+print(first is second)
+print(iter(first) is first)
+```
+
+```output
+False
+True
+```
+:::
+
+:::cell
+The distinction shows up at API boundaries. A function that loops over its argument twice works for an iterable but silently produces wrong answers for an iterator, because the second pass finds the iterator already exhausted. Materialize once at the boundary when both passes matter.
+
+```python
+def total_and_count(numbers):
+ total = sum(numbers)
+ count = sum(1 for _ in numbers)
+ return total, count
+
+def values():
+ yield from [10, 9, 8]
+
+print(total_and_count([10, 9, 8]))
+print(total_and_count(values()))
+
+def total_and_count_safe(numbers):
+ items = list(numbers)
+ return sum(items), len(items)
+
+print(total_and_count_safe(values()))
+```
+
+```output
+(27, 3)
+(27, 0)
+(27, 3)
+```
+:::
+
+:::note
+- An iterable produces an iterator each time `iter()` is called on it; an iterator produces values until it is exhausted.
+- `iter(iterable)` returns a fresh iterator; `iter(iterator)` returns the same iterator.
+- Functions that traverse their input more than once must accept an iterable or materialize the input at the boundary.
+:::
diff --git a/src/example_sources/iterators.md b/src/example_sources/iterators.md
index 2be9f2e..d6b46c2 100644
--- a/src/example_sources/iterators.md
+++ b/src/example_sources/iterators.md
@@ -4,6 +4,11 @@ title = "Iterators"
section = "Iteration"
summary = "iter and next expose the protocol behind for loops."
doc_path = "/library/stdtypes.html#iterator-types"
+see_also = [
+ "iterating-over-iterables",
+ "iterator-vs-iterable",
+ "generators",
+]
+++
An iterable is an object that can produce values for a loop. An iterator is the object that remembers where that production currently is.
diff --git a/src/example_sources/literal-and-final.md b/src/example_sources/literal-and-final.md
index 1abb562..a3074d1 100644
--- a/src/example_sources/literal-and-final.md
+++ b/src/example_sources/literal-and-final.md
@@ -6,11 +6,11 @@ summary = "Literal restricts values, while Final marks names that should not be
doc_path = "/library/typing.html#typing.Literal"
+++
-Literal restricts values, while Final marks names that should not be rebound. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.
+`Literal` restricts a value to one of a small set of exact options, and `Final` tells the type checker that a name should not be rebound. Both are static promises that type checkers enforce; Python's runtime assignment rules still permit any value through if a program ignores the annotation.
-Use it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.
+Use them when an annotation makes a constant or a small option set explicit at the API boundary. Prefer simpler neighboring tools when the extra machinery would hide the intent.
-The example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.
+`Literal` pairs naturally with type aliases and overloads when a function should accept only a known set of strings or numbers. `Final` is most useful for module-level constants and class attributes that the rest of the codebase should treat as immutable.
:::program
```python
diff --git a/src/example_sources/literals.md b/src/example_sources/literals.md
index f167b79..b2a21c4 100644
--- a/src/example_sources/literals.md
+++ b/src/example_sources/literals.md
@@ -7,8 +7,8 @@ doc_path = "/reference/lexical_analysis.html#literals"
see_also = [
"values",
"strings",
- "bytes-and-bytearray",
- "dicts",
+ "numbers",
+ "string-formatting",
]
+++
@@ -25,12 +25,20 @@ fraction = 3.5
complex_number = 2 + 3j
print(whole, fraction, complex_number.imag)
+flags = 0xFF
+mask = 0b1010
+million = 1_000_000
+print(flags, mask, million)
+
text = "python"
raw_pattern = r"\d+"
data = b"py"
+score = 98
+formatted = f"score={score}"
print(text)
print(raw_pattern)
print(data)
+print(formatted)
point = (2, 3)
names = ["Ada", "Grace"]
@@ -43,6 +51,10 @@ print(sorted(unique))
print(True, False, None)
print(...)
+
+print(type({}).__name__)
+print(type(set()).__name__)
+print(type({1, 2}).__name__)
```
:::
@@ -62,21 +74,40 @@ print(whole, fraction, complex_number.imag)
:::
:::cell
-String literals write Unicode text. Raw strings keep backslashes literal, and bytes literals write binary data rather than text.
+Integer literals also accept hexadecimal (`0x`), binary (`0b`), and octal (`0o`) prefixes. Underscores group digits visually without changing the value.
+
+```python
+flags = 0xFF
+mask = 0b1010
+million = 1_000_000
+print(flags, mask, million)
+```
+
+```output
+255 10 1000000
+```
+:::
+
+:::cell
+String literals write Unicode text. Raw strings keep backslashes literal, bytes literals write binary data rather than text, and f-strings (`f"..."`) embed expressions inline.
```python
text = "python"
raw_pattern = r"\d+"
data = b"py"
+score = 98
+formatted = f"score={score}"
print(text)
print(raw_pattern)
print(data)
+print(formatted)
```
```output
python
\d+
b'py'
+score=98
```
:::
@@ -116,6 +147,22 @@ Ellipsis
```
:::
+:::cell
+Curly-brace literals are dictionaries by default. The empty form `{}` is an empty dictionary, not an empty set; use `set()` for that. A non-empty `{1, 2}` is a set because keyless items can only be a set.
+
+```python
+print(type({}).__name__)
+print(type(set()).__name__)
+print(type({1, 2}).__name__)
+```
+
+```output
+dict
+set
+set
+```
+:::
+
:::note
- Literals are good for small local values; constants are better for repeated values with meaning.
- `{}` is an empty dictionary. Use `set()` for an empty set.
diff --git a/src/example_sources/logging.md b/src/example_sources/logging.md
index b08a0f3..d2c55fe 100644
--- a/src/example_sources/logging.md
+++ b/src/example_sources/logging.md
@@ -6,11 +6,11 @@ summary = "logging records operational events without using print as infrastruct
doc_path = "/library/logging.html"
+++
-logging records operational events without using print as infrastructure. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.
+`logging` records operational events without using `print` as infrastructure. Loggers name where each event came from, handlers route records to outputs, and levels (`DEBUG`, `INFO`, `WARNING`, `ERROR`) let operators choose how much detail they want to see.
-Use it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.
+Use it when a program needs structured records with thresholds — production services, command-line tools, scheduled jobs. Prefer plain `print` when a small script just needs to show a line of human output.
-The example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.
+The example wires a single stream handler to stdout to keep the output deterministic. Real applications usually configure logging once at startup and then call `logging.getLogger(__name__)` from each module.
:::program
```python
diff --git a/src/example_sources/manifest.toml b/src/example_sources/manifest.toml
index 811cfb0..ea03446 100644
--- a/src/example_sources/manifest.toml
+++ b/src/example_sources/manifest.toml
@@ -26,6 +26,7 @@ order = [
"loop-else",
"iterating-over-iterables",
"iterators",
+ "iterator-vs-iterable",
"sentinel-iteration",
"match-statements",
"advanced-match-patterns",
@@ -58,6 +59,7 @@ order = [
"decorators",
"classes",
"inheritance-and-super",
+ "classmethods-and-staticmethods",
"dataclasses",
"properties",
"special-methods",
@@ -66,6 +68,7 @@ order = [
"callable-objects",
"operator-overloading",
"attribute-access",
+ "bound-and-unbound-methods",
"descriptors",
"metaclasses",
"context-managers",
@@ -84,6 +87,7 @@ order = [
"union-and-optional-types",
"type-aliases",
"typed-dicts",
+ "structured-data-shapes",
"literal-and-final",
"callable-types",
"generics-and-typevar",
@@ -92,6 +96,7 @@ order = [
"casts-and-any",
"newtype",
"protocols",
+ "abstract-base-classes",
"enums",
"regular-expressions",
"number-parsing",
diff --git a/src/example_sources/numbers.md b/src/example_sources/numbers.md
index cef899b..bc67f7f 100644
--- a/src/example_sources/numbers.md
+++ b/src/example_sources/numbers.md
@@ -18,6 +18,8 @@ Use rounding for display, not as a substitute for understanding floating-point a
:::program
```python
+import math
+
count = 10
ratio = 0.25
z = 2 + 3j
@@ -29,6 +31,8 @@ print(count % 4)
print(2 ** 5)
print(z.real, z.imag)
print(0.1 + 0.2)
+print(0.1 + 0.2 == 0.3)
+print(math.isclose(0.1 + 0.2, 0.3))
print(round(3.14159, 2))
```
:::
@@ -82,15 +86,21 @@ print(z.real, z.imag)
:::
:::cell
-Floating-point values are approximate. Round for display when the exact binary representation is not the lesson.
+Floating-point values are approximate, so `==` between expected and computed floats is rarely the right test. Compare with `math.isclose` (or work in `decimal.Decimal`) when the question is "are these the same number to within tolerance".
```python
+import math
+
print(0.1 + 0.2)
+print(0.1 + 0.2 == 0.3)
+print(math.isclose(0.1 + 0.2, 0.3))
print(round(3.14159, 2))
```
```output
0.30000000000000004
+False
+True
3.14
```
:::
@@ -99,5 +109,5 @@ print(round(3.14159, 2))
- Python's `int` has arbitrary precision; it grows as large as memory allows.
- Python's `float` is approximate double-precision floating point.
- Use `/` for true division and `//` for floor division.
-- Use `decimal.Decimal` when decimal precision is the domain requirement, not just display polish.
+- Use `math.isclose` instead of `==` for floating-point comparison; reach for `decimal.Decimal` when exact decimal precision is the domain requirement.
:::
diff --git a/src/example_sources/operators.md b/src/example_sources/operators.md
index 9e9c7ad..ef01c29 100644
--- a/src/example_sources/operators.md
+++ b/src/example_sources/operators.md
@@ -6,7 +6,7 @@ summary = "Operators combine, compare, and test values in expressions."
doc_path = "/reference/expressions.html#operator-precedence"
see_also = [
"numbers",
- "conditionals",
+ "equality-and-identity",
"assignment-expressions",
"operator-overloading",
]
@@ -32,6 +32,8 @@ print(score == 100 or score >= 90)
print("py" in "python")
flags = 0b0011
+print(flags & 0b0101)
+print(flags | 0b0100)
print(flags ^ 0b0101)
print(flags << 1)
@@ -87,15 +89,19 @@ True
:::
:::cell
-Bitwise operators work on integer bit patterns. They are useful for masks and flags, not ordinary boolean logic.
+Bitwise operators work on integer bit patterns. They are useful for masks and flags, not ordinary boolean logic. `&` is bitwise AND, `|` is bitwise OR, `^` is exclusive OR, and `<<` shifts left.
```python
flags = 0b0011
+print(flags & 0b0101)
+print(flags | 0b0100)
print(flags ^ 0b0101)
print(flags << 1)
```
```output
+1
+7
6
6
```
@@ -134,6 +140,27 @@ if (size := len(items)) > 0:
```
:::
+:::cell
+`and` and `or` short-circuit: the right side runs only when the left side cannot already determine the result. That makes them safe for guard expressions like `obj and obj.value` where the right side would fail on `None`.
+
+```python
+def loud():
+ print("ran")
+ return True
+
+print(False and loud())
+print(True or loud())
+print(True and loud())
+```
+
+```output
+False
+True
+ran
+True
+```
+:::
+
:::note
- Use the clearest operator for the question: arithmetic, comparison, boolean logic, membership, identity, or bitwise manipulation.
- `and` and `or` short-circuit, so the right side may not run.
diff --git a/src/example_sources/packages.md b/src/example_sources/packages.md
index b605714..eac739b 100644
--- a/src/example_sources/packages.md
+++ b/src/example_sources/packages.md
@@ -4,6 +4,11 @@ title = "Packages"
section = "Modules"
summary = "Packages organize modules into importable directories."
doc_path = "/tutorial/modules.html#packages"
+see_also = [
+ "modules",
+ "import-aliases",
+ "virtual-environments",
+]
+++
Packages are modules that can contain other modules. They let a project group related code behind dotted import paths such as `json.decoder` or `email.message`.
@@ -24,6 +29,28 @@ print(json.__name__)
print(json.decoder.__name__)
print(module.JSONDecoder.__name__)
print(module is json.decoder)
+
+
+import os
+import sys
+import tempfile
+
+with tempfile.TemporaryDirectory() as tmp:
+ pkg = os.path.join(tmp, "shapes")
+ os.makedirs(pkg)
+ with open(os.path.join(pkg, "__init__.py"), "w") as init:
+ init.write("from .square import area\n__all__ = ['area']\n")
+ with open(os.path.join(pkg, "square.py"), "w") as square:
+ square.write("def area(side):\n return side * side\n")
+ sys.path.insert(0, tmp)
+ try:
+ import shapes
+ print(shapes.area(3))
+ print(shapes.__all__)
+ finally:
+ sys.path.remove(tmp)
+ sys.modules.pop("shapes", None)
+ sys.modules.pop("shapes.square", None)
```
:::
@@ -72,8 +99,41 @@ True
```
:::
+:::cell
+Inside a package's `__init__.py`, `from .submodule import name` re-exports a submodule's name at the package root, and `__all__` lists the names that `from package import *` should make visible. This cell builds a temporary `shapes` package on disk to make both forms concrete.
+
+```python
+import os
+import sys
+import tempfile
+
+with tempfile.TemporaryDirectory() as tmp:
+ pkg = os.path.join(tmp, "shapes")
+ os.makedirs(pkg)
+ with open(os.path.join(pkg, "__init__.py"), "w") as init:
+ init.write("from .square import area\n__all__ = ['area']\n")
+ with open(os.path.join(pkg, "square.py"), "w") as square:
+ square.write("def area(side):\n return side * side\n")
+ sys.path.insert(0, tmp)
+ try:
+ import shapes
+ print(shapes.area(3))
+ print(shapes.__all__)
+ finally:
+ sys.path.remove(tmp)
+ sys.modules.pop("shapes", None)
+ sys.modules.pop("shapes.square", None)
+```
+
+```output
+9
+['area']
+```
+:::
+
:::note
- A package is a module that can contain submodules.
- Dotted imports should mirror a meaningful project structure.
+- Use `from .submodule import name` inside a package to re-export submodule names; set `__all__` to declare the public surface.
- Prefer ordinary imports unless the module name is truly dynamic.
:::
diff --git a/src/example_sources/paramspec.md b/src/example_sources/paramspec.md
index aac5c89..3d2c33e 100644
--- a/src/example_sources/paramspec.md
+++ b/src/example_sources/paramspec.md
@@ -6,11 +6,11 @@ summary = "ParamSpec preserves callable parameter types through wrappers."
doc_path = "/library/typing.html#typing.ParamSpec"
+++
-ParamSpec preserves callable parameter types through wrappers. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.
+`ParamSpec` lets a wrapper preserve the parameter types of the function it wraps. The pressure that justifies it is decorators: a generic decorator that returns `Callable[..., R]` erases the wrapped function's argument types, so callers lose type-checker help on every call.
-Use it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.
+Use `ParamSpec` when a decorator should be transparent to type checkers — the wrapped function and the decorated name should accept the same arguments. Reach for plain `Callable` when the wrapper deliberately changes the signature.
-The example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.
+`P.args` and `P.kwargs` annotate the `*args` and `**kwargs` of the inner wrapper, which is how the parameter spec gets bound. Pair `ParamSpec` with a `TypeVar` for the return type when the wrapper should also stay generic over what the wrapped function returns.
:::program
```python
diff --git a/src/example_sources/protocols.md b/src/example_sources/protocols.md
index bb6da5d..6e8dff7 100644
--- a/src/example_sources/protocols.md
+++ b/src/example_sources/protocols.md
@@ -8,6 +8,7 @@ see_also = [
"type-hints",
"classes",
"inheritance-and-super",
+ "abstract-base-classes",
]
+++
diff --git a/src/example_sources/regular-expressions.md b/src/example_sources/regular-expressions.md
index e5ca9f1..122b1b4 100644
--- a/src/example_sources/regular-expressions.md
+++ b/src/example_sources/regular-expressions.md
@@ -4,13 +4,17 @@ title = "Regular Expressions"
section = "Text"
summary = "The re module searches and extracts text using regular expressions."
doc_path = "/library/re.html"
+see_also = [
+ "strings",
+ "string-formatting",
+]
+++
-Regular expressions are a compact language for searching and extracting text patterns. Python's `re` module provides the standard interface.
+Regular expressions are a compact language for searching and extracting text patterns. Python's `re` module provides the standard interface: `re.match` anchors at the start of the string, `re.search` finds the first occurrence anywhere, `re.findall` collects every match, `re.sub` rewrites matches, and `re.compile` reuses a pattern.
Use regex when the pattern has structure: repeated records, alternatives, optional parts, or pieces you want to capture. Prefer ordinary string methods for simple substring checks because simpler code is easier to maintain.
-This page is a first regex pass, not the whole language. It focuses on raw pattern strings, capturing groups, repeated matches, and the boundary where a string method is enough.
+Flags such as `re.IGNORECASE` adjust matching behavior without rewriting the pattern. Pair them with `re.compile` when the same pattern is used repeatedly.
:::program
```python
@@ -25,6 +29,18 @@ for name, score in re.findall(pattern, text):
match = re.search(r"Grace: (\d+)", text)
print(match.group(1))
print("Grace" in text)
+
+start = re.match(r"Ada", text)
+print(start is not None)
+print(re.match(r"Grace", text))
+
+scoreline = re.compile(pattern)
+print(scoreline.findall(text))
+
+casey = "ADA: 11"
+print(re.search(r"ada", casey, flags=re.IGNORECASE).group(0))
+
+print(re.sub(r"\d+", "?", text))
```
:::
@@ -72,8 +88,55 @@ True
```
:::
+:::cell
+`re.match` only matches at the start of the string; `re.search` finds the first match anywhere. Picking the right one keeps anchoring intent visible without an explicit `^`.
+
+```python
+start = re.match(r"Ada", text)
+print(start is not None)
+print(re.match(r"Grace", text))
+```
+
+```output
+True
+None
+```
+:::
+
+:::cell
+`re.compile` produces a reusable pattern object whose methods skip the parser on each call. Reach for it when the same pattern runs in a loop.
+
+```python
+scoreline = re.compile(pattern)
+print(scoreline.findall(text))
+```
+
+```output
+[('Ada', '10'), ('Grace', '9')]
+```
+:::
+
+:::cell
+Flags such as `re.IGNORECASE` adjust matching without changing the pattern. `re.sub` replaces every match with a replacement string and returns the rewritten text.
+
+```python
+casey = "ADA: 11"
+print(re.search(r"ada", casey, flags=re.IGNORECASE).group(0))
+
+print(re.sub(r"\d+", "?", text))
+```
+
+```output
+ADA
+Ada: ?, Grace: ?
+```
+:::
+
:::note
- Use raw strings for regex patterns so backslashes are easier to read.
- Use capturing groups when the point is extraction, not just matching.
+- `re.match` anchors at the start; `re.search` finds the first match anywhere.
+- `re.compile` saves work when the pattern runs more than once.
+- `re.sub` rewrites matches; flags like `re.IGNORECASE` change matching behavior without rewriting the pattern.
- Reach for string methods before regex when the pattern is simple.
:::
diff --git a/src/example_sources/runtime-type-checks.md b/src/example_sources/runtime-type-checks.md
index acdbec6..dc99915 100644
--- a/src/example_sources/runtime-type-checks.md
+++ b/src/example_sources/runtime-type-checks.md
@@ -8,6 +8,7 @@ see_also = [
"type-hints",
"protocols",
"casts-and-any",
+ "abstract-base-classes",
]
+++
diff --git a/src/example_sources/sentinel-iteration.md b/src/example_sources/sentinel-iteration.md
index 2ec7111..d7b0dc6 100644
--- a/src/example_sources/sentinel-iteration.md
+++ b/src/example_sources/sentinel-iteration.md
@@ -6,11 +6,11 @@ summary = "iter(callable, sentinel) repeats calls until a marker value appears."
doc_path = "/library/functions.html#iter"
+++
-iter(callable, sentinel) repeats calls until a marker value appears. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.
+`iter(callable, sentinel)` keeps calling a zero-argument callable and yields each result until the callable returns the sentinel value. It is the right shape for repeated reads from files, sockets, or queues — sources where each call produces the next chunk and a known marker means "no more".
-Use it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.
+Reach for it instead of writing `while True:` plus a manual break when the loop body would do nothing else but read and check. The two-argument form turns a polling callable into something that composes with `for` loops, comprehensions, and other iterator helpers.
-The example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.
+The callable must take no arguments. Wrap a parameterized reader in a small lambda or method that closes over the parameters when the underlying API needs them.
:::program
```python
diff --git a/src/example_sources/special-methods.md b/src/example_sources/special-methods.md
index 6d827d3..7f4a136 100644
--- a/src/example_sources/special-methods.md
+++ b/src/example_sources/special-methods.md
@@ -4,6 +4,12 @@ title = "Special Methods"
section = "Data Model"
summary = "Special methods connect your objects to Python syntax and built-ins."
doc_path = "/reference/datamodel.html#special-method-names"
+see_also = [
+ "container-protocols",
+ "operator-overloading",
+ "callable-objects",
+ "context-managers",
+]
+++
Special methods, often called dunder methods, connect user-defined classes to Python syntax and built-ins such as len(), iter(), and repr().
@@ -27,10 +33,67 @@ class Bag:
def __repr__(self):
return f"Bag({self.items!r})"
+ def __str__(self):
+ return ", ".join(self.items)
+
+ def __eq__(self, other):
+ return isinstance(other, Bag) and self.items == other.items
+
+ def __hash__(self):
+ return hash(tuple(self.items))
+
+ def __lt__(self, other):
+ return len(self.items) < len(other.items)
+
+ def __contains__(self, item):
+ return item in self.items
+
+ def __getitem__(self, index):
+ return self.items[index]
+
+ def __setitem__(self, index, value):
+ self.items[index] = value
+
+ def __bool__(self):
+ return bool(self.items)
+
bag = Bag(["a", "b"])
print(len(bag))
print(list(bag))
print(bag)
+print(repr(bag))
+print(Bag(["a", "b"]) == Bag(["a", "b"]))
+print(Bag(["a"]) < Bag(["a", "b"]))
+print(hash(Bag(["a"])) == hash(Bag(["a"])))
+print("a" in bag)
+print(bag[0])
+bag[1] = "z"
+print(list(bag))
+print(bool(Bag([])))
+
+
+class Multiplier:
+ def __init__(self, factor):
+ self.factor = factor
+
+ def __call__(self, value):
+ return value * self.factor
+
+triple = Multiplier(3)
+print(triple(5))
+
+
+class Trace:
+ def __enter__(self):
+ print("enter")
+ return self
+
+ def __exit__(self, *exc):
+ print("exit")
+ return False
+
+with Trace():
+ print("inside")
```
:::
@@ -95,7 +158,7 @@ print(list(bag))
:::
:::cell
-Implement `__repr__` to give the object a useful developer-facing representation when it is printed or inspected.
+Implement `__repr__` to give the object a useful developer-facing representation when it is printed or inspected. With no `__str__` defined, `print()` falls back to `__repr__`.
```python
class Bag:
@@ -120,7 +183,137 @@ Bag(['a', 'b'])
```
:::
+:::cell
+Add `__str__` for an end-user representation. `print()` and `str()` prefer `__str__`; `repr()` and the REPL still use `__repr__`. Keep `__repr__` unambiguous for debugging and let `__str__` be the friendly form.
+
+```python
+class Bag:
+ def __init__(self, items):
+ self.items = list(items)
+
+ def __repr__(self):
+ return f"Bag({self.items!r})"
+
+ def __str__(self):
+ return ", ".join(self.items)
+
+bag = Bag(["a", "b"])
+print(bag)
+print(repr(bag))
+```
+
+```output
+a, b
+Bag(['a', 'b'])
+```
+:::
+
+:::cell
+`__eq__` decides what equality means for the type. Defining `__eq__` removes the default `__hash__`, so add `__hash__` back when instances should work in sets or as dict keys. `__lt__` enables `<` and, with the rest of the order family, `sorted()`.
+
+```python
+class Bag:
+ def __init__(self, items):
+ self.items = list(items)
+
+ def __eq__(self, other):
+ return isinstance(other, Bag) and self.items == other.items
+
+ def __hash__(self):
+ return hash(tuple(self.items))
+
+ def __lt__(self, other):
+ return len(self.items) < len(other.items)
+
+print(Bag(["a", "b"]) == Bag(["a", "b"]))
+print(Bag(["a"]) < Bag(["a", "b"]))
+print(hash(Bag(["a"])) == hash(Bag(["a"])))
+```
+
+```output
+True
+True
+True
+```
+:::
+
+:::cell
+The container protocols make instances behave like built-in containers. `__contains__` powers `in`, `__getitem__`/`__setitem__` power subscription, and `__bool__` decides truthiness for `if` and `while`. See [container-protocols](/data-model/container-protocols) for the full surface.
+
+```python
+class Bag:
+ def __init__(self, items):
+ self.items = list(items)
+
+ def __contains__(self, item):
+ return item in self.items
+
+ def __getitem__(self, index):
+ return self.items[index]
+
+ def __setitem__(self, index, value):
+ self.items[index] = value
+
+ def __bool__(self):
+ return bool(self.items)
+
+bag = Bag(["a", "b"])
+print("a" in bag)
+print(bag[0])
+bag[1] = "z"
+print(bag.items)
+print(bool(Bag([])))
+```
+
+```output
+True
+a
+['a', 'z']
+False
+```
+:::
+
+:::cell
+`__call__` makes an instance callable like a function — useful for stateful operations whose configuration deserves a name. `__enter__` and `__exit__` make a class a context manager so it can be used with `with`. The focused [callable-objects](/data-model/callable-objects) and [context-managers](/data-model/context-managers) pages go deeper.
+
+```python
+class Multiplier:
+ def __init__(self, factor):
+ self.factor = factor
+
+ def __call__(self, value):
+ return value * self.factor
+
+triple = Multiplier(3)
+print(triple(5))
+
+
+class Trace:
+ def __enter__(self):
+ print("enter")
+ return self
+
+ def __exit__(self, *exc):
+ print("exit")
+ return False
+
+with Trace():
+ print("inside")
+```
+
+```output
+15
+enter
+inside
+exit
+```
+:::
+
:::note
- Dunder methods are looked up by Python's data model protocols.
+- `__repr__` is the developer-facing form; `__str__` is the user-facing form. `print()` falls back to `__repr__` when `__str__` is missing.
+- Defining `__eq__` removes the default `__hash__`; restore it when the type should be hashable.
+- Container protocols (`__contains__`, `__getitem__`, `__setitem__`, `__bool__`) make instances behave like built-in containers.
+- `__call__` makes instances callable; `__enter__`/`__exit__` make them context managers.
- Implement the smallest protocol that makes your object feel native.
:::
diff --git a/src/example_sources/structured-data-shapes.md b/src/example_sources/structured-data-shapes.md
new file mode 100644
index 0000000..a44bf43
--- /dev/null
+++ b/src/example_sources/structured-data-shapes.md
@@ -0,0 +1,150 @@
++++
+slug = "structured-data-shapes"
+title = "Structured Data Shapes"
+section = "Classes"
+summary = "dataclass, NamedTuple, and TypedDict each model records with different trade-offs."
+doc_path = "/library/dataclasses.html"
+see_also = [
+ "dataclasses",
+ "typed-dicts",
+ "tuples",
+ "classes",
+]
++++
+
+`@dataclass`, `typing.NamedTuple`, and `typing.TypedDict` are three ways to give a record a name and a schema. They model the same data but differ in mutability, access syntax, and what the type information costs at runtime.
+
+A dataclass is a regular class with `__init__` and `__repr__` generated for you, so instances are mutable and attribute-accessed. A `NamedTuple` is a tuple subclass with named positions, so instances are immutable and support both `obj.field` and `obj[index]`. A `TypedDict` is a plain dict at runtime; the schema lives only in the type checker.
+
+Pick the shape that matches the problem: a dataclass when methods or mutability help; a `NamedTuple` for small immutable records that benefit from unpacking; a `TypedDict` for JSON-shaped data that should stay as a dict at the boundary.
+
+:::program
+```python
+from dataclasses import dataclass
+from typing import NamedTuple, TypedDict
+
+@dataclass
+class UserClass:
+ name: str
+ score: int
+
+class UserTuple(NamedTuple):
+ name: str
+ score: int
+
+class UserDict(TypedDict):
+ name: str
+ score: int
+
+a = UserClass("Ada", 98)
+print(a)
+a.score = 100
+print(a.score)
+
+b = UserTuple("Ada", 98)
+print(b)
+print(b.name, b[1])
+print(b._replace(score=100))
+
+c: UserDict = {"name": "Ada", "score": 98}
+print(c)
+print(c["name"])
+print(type(c).__name__)
+
+print(isinstance(a, UserClass))
+print(isinstance(b, tuple))
+print(isinstance(c, dict))
+```
+:::
+
+:::cell
+A dataclass is a normal class with `__init__` and `__repr__` generated from the annotated fields. Instances are mutable, support attribute access, and can carry methods like any other class.
+
+```python
+from dataclasses import dataclass
+
+@dataclass
+class UserClass:
+ name: str
+ score: int
+
+a = UserClass("Ada", 98)
+print(a)
+a.score = 100
+print(a.score)
+```
+
+```output
+UserClass(name='Ada', score=98)
+100
+```
+:::
+
+:::cell
+A `NamedTuple` is a tuple subclass with named positions. Instances are immutable, support both `obj.field` and `obj[index]`, and the helper `_replace` produces a modified copy without mutating the original (since assigning to a field would fail).
+
+```python
+from typing import NamedTuple
+
+class UserTuple(NamedTuple):
+ name: str
+ score: int
+
+b = UserTuple("Ada", 98)
+print(b)
+print(b.name, b[1])
+print(b._replace(score=100))
+```
+
+```output
+UserTuple(name='Ada', score=98)
+Ada 98
+UserTuple(name='Ada', score=100)
+```
+:::
+
+:::cell
+A `TypedDict` is a plain dictionary at runtime. The annotations exist only for the type checker, so the value behaves like any `dict` — useful for JSON-shaped data that crosses an API boundary as a mapping.
+
+```python
+from typing import TypedDict
+
+class UserDict(TypedDict):
+ name: str
+ score: int
+
+c: UserDict = {"name": "Ada", "score": 98}
+print(c)
+print(c["name"])
+print(type(c).__name__)
+```
+
+```output
+{'name': 'Ada', 'score': 98}
+Ada
+dict
+```
+:::
+
+:::cell
+Same record, three runtime identities. The dataclass is its own class. The `NamedTuple` is literally a tuple. The `TypedDict` is literally a dict. That difference drives the choice: pick the form whose runtime behavior matches what the rest of the program already expects.
+
+```python
+print(isinstance(a, UserClass))
+print(isinstance(b, tuple))
+print(isinstance(c, dict))
+```
+
+```output
+True
+True
+True
+```
+:::
+
+:::note
+- `@dataclass` — mutable, attribute access, methods; good default when behavior travels with data.
+- `typing.NamedTuple` — immutable, attribute + index access, tuple semantics; good for small records that flow through unpacking.
+- `typing.TypedDict` — runtime is `dict`, schema is type-checker-only; good for JSON-shaped data.
+- `collections.namedtuple` is the older, untyped form of `NamedTuple`; prefer the `typing` version in new code.
+:::
diff --git a/src/example_sources/testing.md b/src/example_sources/testing.md
index 0e5e27e..e1776ce 100644
--- a/src/example_sources/testing.md
+++ b/src/example_sources/testing.md
@@ -4,6 +4,11 @@ title = "Testing"
section = "Standard Library"
summary = "Tests make expected behavior executable and repeatable."
doc_path = "/library/unittest.html"
+see_also = [
+ "assertions",
+ "exceptions",
+ "modules",
+]
+++
Tests turn expected behavior into code that can be run again. The useful unit is usually a small example of behavior with clear input, action, and assertion.
@@ -21,13 +26,27 @@ import unittest
def add(left, right):
return left + right
+
+def divide(left, right):
+ if right == 0:
+ raise ZeroDivisionError("denominator is zero")
+ return left / right
+
+
class AddTests(unittest.TestCase):
+ def setUp(self):
+ self.zero = 0
+
def test_adds_numbers(self):
- self.assertEqual(add(2, 3), 5)
+ self.assertEqual(add(self.zero + 2, 3), 5)
def test_adds_empty_strings(self):
self.assertEqual(add("", "py"), "py")
+ def test_divide_by_zero_raises(self):
+ with self.assertRaises(ZeroDivisionError):
+ divide(1, 0)
+
suite = unittest.defaultTestLoader.loadTestsFromTestCase(AddTests)
stream = io.StringIO()
result = unittest.TextTestRunner(stream=stream, verbosity=0).run(suite)
@@ -52,23 +71,37 @@ print(add(2, 3))
:::
:::cell
-`unittest.TestCase` groups test methods. Assertion methods such as `assertEqual` make the expected behavior explicit.
+`unittest.TestCase` groups test methods. `setUp` runs before each test method to build per-test fixtures, `assertEqual` checks values, and `assertRaises` asserts that a block raises the expected exception type.
```python
import unittest
+
+def divide(left, right):
+ if right == 0:
+ raise ZeroDivisionError("denominator is zero")
+ return left / right
+
+
class AddTests(unittest.TestCase):
+ def setUp(self):
+ self.zero = 0
+
def test_adds_numbers(self):
- self.assertEqual(add(2, 3), 5)
+ self.assertEqual(add(self.zero + 2, 3), 5)
def test_adds_empty_strings(self):
self.assertEqual(add("", "py"), "py")
+ def test_divide_by_zero_raises(self):
+ with self.assertRaises(ZeroDivisionError):
+ divide(1, 0)
+
print([name for name in dir(AddTests) if name.startswith("test_")])
```
```output
-['test_adds_empty_strings', 'test_adds_numbers']
+['test_adds_empty_strings', 'test_adds_numbers', 'test_divide_by_zero_raises']
```
:::
@@ -86,7 +119,7 @@ print(result.wasSuccessful())
```
```output
-2
+3
True
```
:::
diff --git a/src/example_sources/tuples.md b/src/example_sources/tuples.md
index 5488e6c..c666605 100644
--- a/src/example_sources/tuples.md
+++ b/src/example_sources/tuples.md
@@ -4,6 +4,11 @@ title = "Tuples"
section = "Collections"
summary = "Tuples group a fixed number of positional values."
doc_path = "/tutorial/datastructures.html#tuples-and-sequences"
+see_also = [
+ "lists",
+ "unpacking",
+ "structured-data-shapes",
+]
+++
Tuples are ordered, immutable sequences. They exist for small fixed groups where position has meaning: coordinates, RGB colors, database rows, and multiple return values.
@@ -25,6 +30,14 @@ print(len(red))
record = ("Ada", 10)
name, score = record
print(f"{name}: {score}")
+
+scores = [10, 9, 8]
+scores.append(7)
+print(scores)
+
+student = ("Ada", 2024, "math")
+name, year, subject = student
+print(name, year, subject)
```
:::
@@ -71,8 +84,28 @@ Ada: 10
```
:::
+:::cell
+Lists and tuples carry different intent. A list holds a variable number of similar items and grows with `append`; a tuple has a fixed shape where each position has its own meaning, and unpacking gives those positions names.
+
+```python
+scores = [10, 9, 8]
+scores.append(7)
+print(scores)
+
+student = ("Ada", 2024, "math")
+name, year, subject = student
+print(name, year, subject)
+```
+
+```output
+[10, 9, 8, 7]
+Ada 2024 math
+```
+:::
+
:::note
- Tuples are immutable sequences with fixed length.
- Use tuples for small records where position has meaning.
- Use lists for variable-length collections of similar items.
+- Reach for a dataclass or `NamedTuple` when fields deserve names everywhere they're used.
:::
diff --git a/src/example_sources/type-hints.md b/src/example_sources/type-hints.md
index f9bfc66..5e50641 100644
--- a/src/example_sources/type-hints.md
+++ b/src/example_sources/type-hints.md
@@ -4,6 +4,12 @@ title = "Type Hints"
section = "Types"
summary = "Annotations document expected types and power static analysis."
doc_path = "/library/typing.html"
+see_also = [
+ "union-and-optional-types",
+ "type-aliases",
+ "generics-and-typevar",
+ "runtime-type-checks",
+]
+++
Type hints are annotations that document expected shapes for values, parameters, and return results. They exist so tools and readers can understand API boundaries before the program runs.
@@ -14,6 +20,8 @@ The alternative to an annotation is prose, tests, or runtime validation. Good Py
:::program
```python
+from typing import TypeAlias
+
def total(numbers: list[int]) -> int:
return sum(numbers)
@@ -25,6 +33,31 @@ def label(score: int) -> str:
return f"score={score}"
print(label("high"))
+
+
+def find(name: str, options: list[str]) -> str | None:
+ return name if name in options else None
+
+print(find("Ada", ["Ada", "Grace"]))
+print(find("Guido", ["Ada", "Grace"]))
+
+
+from typing import Optional
+
+def lookup(name: str) -> Optional[int]:
+ table = {"Ada": 1815, "Grace": 1906}
+ return table.get(name)
+
+print(lookup("Ada"))
+print(lookup("Guido"))
+
+
+Score: TypeAlias = int
+
+def grade(score: Score) -> str:
+ return "pass" if score >= 50 else "fail"
+
+print(grade(72))
```
:::
@@ -70,8 +103,58 @@ score=high
```
:::
+:::cell
+Use `X | Y` (PEP 604) to express "either type". `str | None` says the result is a string or absent. `typing.Optional[X]` is the older, still-supported spelling for the same idea — `Optional[X]` is equivalent to `X | None`.
+
+```python
+def find(name: str, options: list[str]) -> str | None:
+ return name if name in options else None
+
+print(find("Ada", ["Ada", "Grace"]))
+print(find("Guido", ["Ada", "Grace"]))
+
+
+from typing import Optional
+
+def lookup(name: str) -> Optional[int]:
+ table = {"Ada": 1815, "Grace": 1906}
+ return table.get(name)
+
+print(lookup("Ada"))
+print(lookup("Guido"))
+```
+
+```output
+Ada
+None
+1815
+None
+```
+:::
+
+:::cell
+`TypeAlias` names a type so it can be reused with intent. `Score: TypeAlias = int` keeps the underlying type at runtime but lets the API talk about a domain concept rather than a primitive.
+
+```python
+from typing import TypeAlias
+
+Score: TypeAlias = int
+
+def grade(score: Score) -> str:
+ return "pass" if score >= 50 else "fail"
+
+print(grade(72))
+```
+
+```output
+pass
+```
+:::
+
:::note
- Python does not enforce most type hints at runtime.
- Tools like type checkers and editors use annotations to catch mistakes earlier.
+- Use `X | Y` for unions and `Optional[X]` for "X or None"; both spellings mean the same thing.
+- Reach for `TypeAlias` when a domain name reads better than a raw primitive type.
- Use runtime validation when untrusted input must be rejected while the program runs.
:::
diff --git a/src/example_sources/typed-dicts.md b/src/example_sources/typed-dicts.md
index d98236f..8f59c9b 100644
--- a/src/example_sources/typed-dicts.md
+++ b/src/example_sources/typed-dicts.md
@@ -8,6 +8,7 @@ see_also = [
"dicts",
"json",
"dataclasses",
+ "structured-data-shapes",
]
+++
diff --git a/src/example_sources_data.py b/src/example_sources_data.py
index aa0169b..58dbf0f 100644
--- a/src/example_sources_data.py
+++ b/src/example_sources_data.py
@@ -1,2 +1,2 @@
# Generated by scripts/embed_example_sources.py. Do not edit by hand.
-EXAMPLE_SOURCE_FILES = {'manifest.toml': 'python_version = "3.13"\ndocs_base_url = "https://docs.python.org/3.13"\n\norder = [\n "hello-world",\n "values",\n "literals",\n "numbers",\n "booleans",\n "operators",\n "none",\n "variables",\n "constants",\n "truthiness",\n "equality-and-identity",\n "mutability",\n "object-lifecycle",\n "strings",\n "bytes-and-bytearray",\n "string-formatting",\n "conditionals",\n "guard-clauses",\n "assignment-expressions",\n "for-loops",\n "break-and-continue",\n "loop-else",\n "iterating-over-iterables",\n "iterators",\n "sentinel-iteration",\n "match-statements",\n "advanced-match-patterns",\n "while-loops",\n "lists",\n "tuples",\n "unpacking",\n "dicts",\n "sets",\n "slices",\n "comprehensions",\n "comprehension-patterns",\n "sorting",\n "collections-module",\n "copying-collections",\n "functions",\n "keyword-only-arguments",\n "positional-only-parameters",\n "args-and-kwargs",\n "multiple-return-values",\n "closures",\n "partial-functions",\n "scope-global-nonlocal",\n "recursion",\n "lambdas",\n "generators",\n "yield-from",\n "generator-expressions",\n "itertools",\n "decorators",\n "classes",\n "inheritance-and-super",\n "dataclasses",\n "properties",\n "special-methods",\n "truth-and-size",\n "container-protocols",\n "callable-objects",\n "operator-overloading",\n "attribute-access",\n "descriptors",\n "metaclasses",\n "context-managers",\n "delete-statements",\n "exceptions",\n "assertions",\n "exception-chaining",\n "exception-groups",\n "warnings",\n "modules",\n "import-aliases",\n "packages",\n "virtual-environments",\n "type-hints",\n "runtime-type-checks",\n "union-and-optional-types",\n "type-aliases",\n "typed-dicts",\n "literal-and-final",\n "callable-types",\n "generics-and-typevar",\n "paramspec",\n "overloads",\n "casts-and-any",\n "newtype",\n "protocols",\n "enums",\n "regular-expressions",\n "number-parsing",\n "custom-exceptions",\n "json",\n "logging",\n "testing",\n "subprocesses",\n "threads-and-processes",\n "networking",\n "datetime",\n "csv-data",\n "async-await",\n "async-iteration-and-context",\n]\n', 'advanced-match-patterns.md': '+++\nslug = "advanced-match-patterns"\ntitle = "Advanced Match Patterns"\nsection = "Control Flow"\nsummary = "match patterns can destructure sequences, combine alternatives, and add guards."\ndoc_path = "/tutorial/controlflow.html#match-statements"\nsee_also = [\n "match-statements",\n "tuples",\n "classes",\n]\n+++\n\nStructural pattern matching is more than equality checks. Patterns can destructure sequences, match several alternatives, capture the rest of a sequence, and use guards.\n\nUse these forms when the shape of data is the decision. If the decision is only a single boolean condition, ordinary `if` statements are usually clearer.\n\nThe wildcard `_` catches everything not matched earlier.\n\n:::program\n```python\ndef describe(command):\n match command:\n case ["move", x, y] if x >= 0 and y >= 0:\n return f"move to {x},{y}"\n case ["quit" | "exit"]:\n return "stop"\n case ["echo", *words]:\n return " ".join(words)\n case _:\n return "unknown"\n\nprint(describe(["move", 2, 3]))\nprint(describe(["exit"]))\nprint(describe(["echo", "hello", "python"]))\nprint(describe(["move", -1, 3]))\n```\n:::\n\n:::cell\nSequence patterns match by position. A guard after `if` adds a condition that must also be true.\n\n```python\ndef describe(command):\n match command:\n case ["move", x, y] if x >= 0 and y >= 0:\n return f"move to {x},{y}"\n case ["quit" | "exit"]:\n return "stop"\n case ["echo", *words]:\n return " ".join(words)\n case _:\n return "unknown"\n\nprint(describe(["move", 2, 3]))\n```\n\n```output\nmove to 2,3\n```\n:::\n\n:::cell\nAn OR pattern accepts several alternatives in one case. A star pattern captures the rest of a sequence.\n\n```python\nprint(describe(["exit"]))\nprint(describe(["echo", "hello", "python"]))\n```\n\n```output\nstop\nhello python\n```\n:::\n\n:::cell\nThe wildcard `_` catches values that did not match earlier cases. Here the guard rejects the negative coordinate.\n\n```python\nprint(describe(["move", -1, 3]))\n```\n\n```output\nunknown\n```\n:::\n\n:::note\n- Use `case _` as a wildcard fallback.\n- Guards refine a pattern after the structure matches.\n- OR patterns and star patterns keep shape-based branches compact.\n:::\n', 'args-and-kwargs.md': '+++\nslug = "args-and-kwargs"\ntitle = "Args and Kwargs"\nsection = "Functions"\nsummary = "*args collects extra positional arguments and **kwargs collects named ones."\ndoc_path = "/tutorial/controlflow.html#arbitrary-argument-lists"\n+++\n\n`*args` and `**kwargs` let a function accept flexible positional and keyword arguments. They are the function-definition counterpart to unpacking at a call site.\n\nThese parameters are useful for wrappers, decorators, logging helpers, and APIs that forward arguments to another function.\n\nThey should not replace clear signatures. If a function has a stable interface, explicit parameters document expectations better than a bag of arguments.\n\n:::program\n```python\ndef total(*numbers):\n return sum(numbers)\n\nprint(total(2, 3, 5))\n\n\ndef describe(**metadata):\n print(metadata)\n\ndescribe(owner="Ada", public=True)\n\n\ndef report(title, *items, **metadata):\n print(title)\n print(items)\n print(metadata)\n\nreport("scores", 10, 9, owner="Ada")\n```\n:::\n\n:::cell\n`*args` collects extra positional arguments into a tuple. This fits functions that naturally accept any number of similar values.\n\n```python\ndef total(*numbers):\n return sum(numbers)\n\nprint(total(2, 3, 5))\n```\n\n```output\n10\n```\n:::\n\n:::cell\n`**kwargs` collects named arguments into a dictionary. The names become string keys.\n\n```python\ndef describe(**metadata):\n print(metadata)\n\ndescribe(owner="Ada", public=True)\n```\n\n```output\n{\'owner\': \'Ada\', \'public\': True}\n```\n:::\n\n:::cell\nA function can combine explicit parameters, `*args`, and `**kwargs`. Put the flexible parts last so the fixed shape remains visible.\n\n```python\ndef report(title, *items, **metadata):\n print(title)\n print(items)\n print(metadata)\n\nreport("scores", 10, 9, owner="Ada")\n```\n\n```output\nscores\n(10, 9)\n{\'owner\': \'Ada\'}\n```\n:::\n\n:::note\n- Use these tools when a function naturally accepts a flexible shape.\n- Prefer explicit parameters when the accepted arguments are known and fixed.\n- `*args` is a tuple; `**kwargs` is a dictionary.\n:::\n', 'assertions.md': '+++\nslug = "assertions"\ntitle = "Assertions"\nsection = "Errors"\nsummary = "assert documents internal assumptions and fails loudly when they are false."\ndoc_path = "/reference/simple_stmts.html#the-assert-statement"\nsee_also = [\n "exceptions",\n "custom-exceptions",\n "type-hints",\n]\n+++\n\n`assert` checks an internal assumption. If the condition is false, Python raises `AssertionError` with an optional message.\n\nUse assertions for programmer assumptions, not for validating user input or external data. Input validation should raise ordinary exceptions that production code expects to handle.\n\nAssertions make invariants executable while keeping the successful path compact.\n\n:::program\n```python\ndef average(scores):\n assert scores, "scores must not be empty"\n return sum(scores) / len(scores)\n\nprint(average([8, 10]))\n\ntry:\n average([])\nexcept AssertionError as error:\n print(error)\n```\n:::\n\n:::cell\nWhen the assertion is true, execution continues normally. The assertion documents the function\'s internal expectation.\n\n```python\ndef average(scores):\n assert scores, "scores must not be empty"\n return sum(scores) / len(scores)\n\nprint(average([8, 10]))\n```\n\n```output\n9.0\n```\n:::\n\n:::cell\nWhen the assertion is false, Python raises `AssertionError`. This signals a broken assumption, not a normal recovery path.\n\n```python\ntry:\n average([])\nexcept AssertionError as error:\n print(error)\n```\n\n```output\nscores must not be empty\n```\n:::\n\n:::note\n- Use `assert` for internal invariants and debugging assumptions.\n- Use explicit exceptions for user input, files, network responses, and other expected failures.\n- Assertions can be disabled with Python optimization flags, so do not rely on them for security checks.\n:::\n', 'assignment-expressions.md': '+++\nslug = "assignment-expressions"\ntitle = "Assignment Expressions"\nsection = "Control Flow"\nsummary = "The walrus operator assigns a value inside an expression."\ndoc_path = "/reference/expressions.html#assignment-expressions"\nsee_also = [\n "conditionals",\n "while-loops",\n "variables",\n]\n+++\n\nThe assignment expression operator `:=` assigns a name while evaluating an expression. It is often called the walrus operator.\n\nUse it when computing a value and testing it are naturally one step. Avoid it when a separate assignment would make the code easier to read.\n\nThe boundary is readability: the walrus operator can remove duplication, but it should not hide important state changes.\n\n:::program\n```python\nmessages = ["hello", "", "python"]\n\nfor message in messages:\n if length := len(message):\n print(message, length)\n\nqueue = ["retry", "ok"]\nwhile (status := queue.pop(0)) != "ok":\n print(status)\nprint(status)\n```\n:::\n\n:::cell\nAn assignment expression can name a computed value while a condition tests it. Here empty strings are skipped because their length is zero.\n\n```python\nmessages = ["hello", "", "python"]\n\nfor message in messages:\n if length := len(message):\n print(message, length)\n```\n\n```output\nhello 5\npython 6\n```\n:::\n\n:::cell\nThe same idea works in loops that read state until a sentinel appears. The assignment and comparison stay together.\n\n```python\nqueue = ["retry", "ok"]\nwhile (status := queue.pop(0)) != "ok":\n print(status)\nprint(status)\n```\n\n```output\nretry\nok\n```\n:::\n\n:::note\n- `name := expression` assigns and evaluates to the assigned value.\n- Use it to avoid computing the same value twice.\n- Prefer a normal assignment when the expression becomes hard to scan.\n:::\n', 'async-await.md': '+++\nslug = "async-await"\ntitle = "Async Await"\nsection = "Async"\nsummary = "async def creates coroutines, and await pauses until awaitable work completes."\ndoc_path = "/library/asyncio-task.html"\n+++\n\n`async def` creates a coroutine function. Calling it creates a coroutine object; the body runs when an event loop awaits or schedules it.\n\n`await` pauses the current coroutine until another awaitable completes. This lets one event loop make progress on other work while a task waits for I/O.\n\nCloudflare Workers handlers are asynchronous, so understanding `await` is practical for fetch calls, bindings, and service interactions even when a small example uses `asyncio.sleep(0)` as a stand-in.\n\nThe alternative is ordinary `def` for work that completes immediately. Use async code for I/O-shaped waiting, not as a faster replacement for CPU-bound Python.\n\n:::program\n```python\nimport asyncio\n\nasync def fetch_title(slug):\n await asyncio.sleep(0)\n return slug.replace("-", " ").title()\n\nasync def main():\n title = await fetch_title("async-await")\n print(title)\n titles = await asyncio.gather(fetch_title("json"), fetch_title("datetime"))\n print(titles)\n\nasyncio.run(main())\n```\n:::\n\n:::cell\nAn `async def` function returns a coroutine object when called. The function body has not produced its final result yet.\n\n```python\nimport asyncio\n\nasync def fetch_title(slug):\n await asyncio.sleep(0)\n return slug.replace("-", " ").title()\n\ncoroutine = fetch_title("async-await")\nprint(coroutine.__class__.__name__)\ncoroutine.close()\n```\n\n```output\ncoroutine\n```\n:::\n\n:::cell\nUse `await` inside another coroutine to get the eventual result. `asyncio.run()` starts an event loop for the top-level coroutine.\n\n```python\nasync def main():\n title = await fetch_title("async-await")\n print(title)\n\nasyncio.run(main())\n```\n\n```output\nAsync Await\n```\n:::\n\n:::cell\n`asyncio.gather()` awaits several awaitables and returns their results in order. This is the shape used when independent I/O operations can progress together.\n\n```python\nasync def main():\n titles = await asyncio.gather(fetch_title("json"), fetch_title("datetime"))\n print(titles)\n\nasyncio.run(main())\n```\n\n```output\n[\'Json\', \'Datetime\']\n```\n:::\n\n:::note\n- Calling an async function creates a coroutine object.\n- `await` yields control until an awaitable completes.\n- Workers request handlers are async, so this pattern appears around fetches and bindings.\n- Prefer ordinary functions when there is no awaitable work to coordinate.\n:::\n', 'async-iteration-and-context.md': '+++\nslug = "async-iteration-and-context"\ntitle = "Async Iteration and Context"\nsection = "Async"\nsummary = "async for and async with consume asynchronous streams and cleanup protocols."\ndoc_path = "/reference/compound_stmts.html#async-for"\nsee_also = [\n "async-await",\n "iterators",\n "context-managers",\n]\n+++\n\n`async for` consumes an asynchronous iterator: a stream whose next value may require `await`. `async with` surrounds a block with asynchronous setup and cleanup.\n\nThese forms appear around network streams, database cursors, locks, and service clients where both iteration and cleanup may wait on I/O.\n\nUse ordinary `for` and `with` when producing the next value or cleaning up does not need to await anything.\n\nThe syntax mirrors `for` and `with`, but the protocol methods are asynchronous.\n\n:::program\n```python\nimport asyncio\n\nasync def titles():\n for slug in ["values", "async-await"]:\n await asyncio.sleep(0)\n yield slug.replace("-", " ").title()\n\nclass Session:\n async def __aenter__(self):\n print("open")\n return self\n\n async def __aexit__(self, exc_type, exc, tb):\n print("close")\n\nasync def main():\n async with Session():\n async for title in titles():\n print(title)\n\nasyncio.run(main())\n```\n:::\n\n:::cell\nAn async generator can `await` before yielding each value. `async for` consumes those values with the asynchronous iteration protocol.\n\n```python\nimport asyncio\n\nasync def titles():\n for slug in ["values", "async-await"]:\n await asyncio.sleep(0)\n yield slug.replace("-", " ").title()\n\nprint(titles.__name__)\n```\n\n```output\ntitles\n```\n:::\n\n:::cell\nAn async context manager defines `__aenter__` and `__aexit__`. `async with` awaits setup and cleanup around the block.\n\n```python\nclass Session:\n async def __aenter__(self):\n print("open")\n return self\n\n async def __aexit__(self, exc_type, exc, tb):\n print("close")\n\nprint(Session.__name__)\n```\n\n```output\nSession\n```\n:::\n\n:::cell\nThe top-level coroutine combines both protocols: open the async resource, then consume the async stream inside it.\n\n```python\nasync def main():\n async with Session():\n async for title in titles():\n print(title)\n\nasyncio.run(main())\n```\n\n```output\nopen\nValues\nAsync Await\nclose\n```\n:::\n\n:::note\n- `async for` consumes asynchronous iterators.\n- `async with` awaits asynchronous setup and cleanup.\n- These forms are common around I/O-shaped resources.\n:::\n', 'attribute-access.md': '+++\nslug = "attribute-access"\ntitle = "Attribute Access"\nsection = "Data Model"\nsummary = "Attribute hooks customize lookup, missing attributes, and assignment."\ndoc_path = "/reference/datamodel.html#customizing-attribute-access"\nsee_also = [\n "properties",\n "descriptors",\n "special-methods",\n]\n+++\n\nAttribute access is usually simple: `obj.name` looks up an attribute. Python exposes hooks for the uncommon cases where lookup or assignment needs to be customized.\n\n`__getattr__` runs only when normal lookup fails, which makes it a safer hook for computed fallback attributes. `__setattr__` runs for every assignment, so it should be used sparingly and carefully.\n\nPrefer ordinary attributes and `@property` first. Reach for these hooks when an object is intentionally adapting another interface, validating all assignments, or exposing dynamic fields.\n\n:::program\n```python\nclass Settings:\n def __init__(self, values):\n self._values = dict(values)\n\n def __getattr__(self, name):\n try:\n return self._values[name]\n except KeyError as error:\n raise AttributeError(name) from error\n\n def __setattr__(self, name, value):\n if name.startswith("_"):\n object.__setattr__(self, name, value)\n else:\n self._values[name] = value\n\nsettings = Settings({"theme": "dark"})\nprint(settings.theme)\nsettings.volume = 7\nprint(settings._values["volume"])\n```\n:::\n\n:::cell\nNormal initialization still needs to set real attributes. Calling `object.__setattr__` avoids recursing through your own hook.\n\n```python\nclass Settings:\n def __init__(self, values):\n self._values = dict(values)\n\nsettings = Settings({"theme": "dark"})\nprint(settings._values)\n```\n\n```output\n{\'theme\': \'dark\'}\n```\n:::\n\n:::cell\n`__getattr__` runs only for missing attributes, so it can provide fallback lookup.\n\n```python\nclass Settings:\n def __init__(self, values):\n self._values = dict(values)\n\n def __getattr__(self, name):\n try:\n return self._values[name]\n except KeyError as error:\n raise AttributeError(name) from error\n\nsettings = Settings({"theme": "dark"})\nprint(settings.theme)\n```\n\n```output\ndark\n```\n:::\n\n:::cell\n`__setattr__` intercepts assignment. This example stores public names in the backing dictionary.\n\n```python\nclass Settings:\n def __init__(self, values):\n self._values = dict(values)\n\n def __setattr__(self, name, value):\n if name.startswith("_"):\n object.__setattr__(self, name, value)\n else:\n self._values[name] = value\n\nsettings = Settings({"theme": "dark"})\nsettings.volume = 7\nprint(settings._values["volume"])\n```\n\n```output\n7\n```\n:::\n\n:::note\n- `__getattr__` is narrower than `__getattribute__` because it handles only missing attributes.\n- `__setattr__` affects every assignment on the instance.\n- Use `property` or descriptors when the behavior is attached to a known attribute name.\n:::\n', 'booleans.md': '+++\nslug = "booleans"\ntitle = "Booleans"\nsection = "Basics"\nsummary = "Booleans represent truth values and combine with logical operators."\ndoc_path = "/library/stdtypes.html#boolean-type-bool"\n+++\n\nBooleans are the values `True` and `False`. They are produced by comparisons and combined with `and`, `or`, and `not`.\n\nPython\'s logical operators short-circuit. That means the right side is evaluated only when needed, which keeps guard checks efficient and safe.\n\nBooleans are also connected to truthiness: many objects can be tested in conditions even when they are not literally `True` or `False`.\n\n:::program\n```python\nlogged_in = True\nhas_permission = False\n\nprint(logged_in and has_permission)\nprint(logged_in or has_permission)\nprint(not has_permission)\n\nname = "Ada"\nprint(name == "Ada" and len(name) > 0)\n```\n:::\n\n:::cell\nUse booleans for facts that are either true or false. Python spells the constants `True` and `False`.\n\nUse `and`, `or`, and `not` to combine truth values. These operators read like English and short-circuit when possible.\n\n```python\nlogged_in = True\nhas_permission = False\n\nprint(logged_in and has_permission)\nprint(logged_in or has_permission)\nprint(not has_permission)\n```\n\n```output\nFalse\nTrue\nTrue\n```\n:::\n\n:::cell\nComparisons produce booleans too, so they compose naturally with logical operators in conditions and validation checks.\n\n```python\nname = "Ada"\nprint(name == "Ada" and len(name) > 0)\n```\n\n```output\nTrue\n```\n:::\n\n:::note\n- Boolean constants are `True` and `False`, with capital letters.\n- `and` and `or` short-circuit: Python does not evaluate the right side if the left side already determines the result.\n- Prefer truthiness for containers and explicit comparisons when the exact boolean condition matters.\n:::\n', 'break-and-continue.md': '+++\nslug = "break-and-continue"\ntitle = "Break and Continue"\nsection = "Control Flow"\nsummary = "break exits a loop early, while continue skips to the next iteration."\ndoc_path = "/tutorial/controlflow.html#break-and-continue-statements"\nsee_also = [\n "for-loops",\n "while-loops",\n "loop-else",\n]\n+++\n\n`break` and `continue` control the nearest enclosing loop. They exist for loops whose body discovers an early stop rule or an item-level skip rule.\n\nUse `continue` when the current item should not run the rest of the body. Use `break` when no later item should be processed.\n\nThe alternative is ordinary `if`/`else` nesting. Prefer `break` and `continue` when they keep the normal path flatter and easier to read.\n\n:::program\n```python\nnames = ["Ada", "", "Grace"]\nfor name in names:\n if not name:\n continue\n print(name)\n\ncommands = ["load", "save", "stop", "delete"]\nfor command in commands:\n if command == "stop":\n break\n print(command)\n```\n:::\n\n:::cell\n`continue` skips the rest of the current iteration. The empty name is ignored, and the loop moves on to the next value.\n\n```python\nnames = ["Ada", "", "Grace"]\nfor name in names:\n if not name:\n continue\n print(name)\n```\n\n```output\nAda\nGrace\n```\n:::\n\n:::cell\n`break` exits the loop immediately. The value after `stop` is never processed because the loop has already ended.\n\n```python\ncommands = ["load", "save", "stop", "delete"]\nfor command in commands:\n if command == "stop":\n break\n print(command)\n```\n\n```output\nload\nsave\n```\n:::\n\n:::note\n- `continue` skips to the next loop iteration.\n- `break` exits the nearest enclosing loop immediately.\n- Prefer plain `if`/`else` when the loop does not need early skip or early stop behavior.\n:::\n', 'bytes-and-bytearray.md': '+++\nslug = "bytes-and-bytearray"\ntitle = "Bytes and Bytearray"\nsection = "Basics"\nsummary = "bytes and bytearray store binary data, not Unicode text."\ndoc_path = "/library/stdtypes.html#binary-sequence-types-bytes-bytearray-memoryview"\nsee_also = [\n "strings",\n "literals",\n "networking",\n]\n+++\n\n`str` stores Unicode text. `bytes` stores raw byte values. The boundary matters whenever text leaves Python for a file, network protocol, subprocess, or binary format.\n\nEncoding turns text into bytes with a named encoding such as UTF-8. Decoding turns bytes back into text. The lengths can differ because one Unicode character may require several bytes.\n\nUse immutable `bytes` for stable binary data and `bytearray` when the bytes must be changed in place.\n\n:::program\n```python\ntext = "café"\ndata = text.encode("utf-8")\n\nprint(data)\nprint(len(text), len(data))\nprint(data.decode("utf-8"))\nprint(data[0])\n\npacket = bytearray(b"py")\npacket[0] = ord("P")\nprint(packet)\n```\n:::\n\n:::cell\nEncode text when an external boundary needs bytes. UTF-8 uses one byte for ASCII characters and more than one byte for many other characters.\n\n```python\ntext = "café"\ndata = text.encode("utf-8")\nprint(data)\nprint(len(text), len(data))\n```\n\n```output\nb\'caf\\xc3\\xa9\'\n4 5\n```\n:::\n\n:::cell\nDecode bytes when the program needs text again. The decoder must match the encoding used at the boundary.\n\n```python\nprint(data.decode("utf-8"))\n```\n\n```output\ncafé\n```\n:::\n\n:::cell\nIndexing a `bytes` object returns an integer byte value, not a one-character `bytes` object.\n\n```python\nprint(data[0])\n```\n\n```output\n99\n```\n:::\n\n:::cell\n`bytes` is immutable. Use `bytearray` when binary data must be changed in place.\n\n```python\npacket = bytearray(b"py")\npacket[0] = ord("P")\nprint(packet)\n```\n\n```output\nbytearray(b\'Py\')\n```\n:::\n\n:::note\n- Encode text when an external boundary needs bytes.\n- Decode bytes when you want text again.\n- Indexing `bytes` returns integers from 0 to 255.\n- Use `bytearray` when binary data must be changed in place.\n:::\n', 'callable-objects.md': '+++\nslug = "callable-objects"\ntitle = "Callable Objects"\nsection = "Data Model"\nsummary = "__call__ lets an instance behave like a function while keeping state."\ndoc_path = "/reference/datamodel.html#object.__call__"\nsee_also = [\n "functions",\n "closures",\n "callable-types",\n]\n+++\n\nFunctions are not the only callable objects in Python. Any instance can be called with parentheses when its class defines `__call__`.\n\nCallable objects are useful when behavior needs remembered configuration or evolving state. A closure can do this too; a class is often clearer when the state has multiple fields or needs named methods.\n\nThe tradeoff is ceremony. Use a function for simple behavior, a closure for small captured state, and a callable object when naming the state improves the interface.\n\n:::program\n```python\nclass Multiplier:\n def __init__(self, factor):\n self.factor = factor\n self.calls = 0\n\n def __call__(self, value):\n self.calls += 1\n return value * self.factor\n\ndouble = Multiplier(2)\nprint(double(5))\nprint(double(7))\nprint(double.calls)\n```\n:::\n\n:::cell\nA callable object starts as ordinary state stored on an instance.\n\n```python\nclass Multiplier:\n def __init__(self, factor):\n self.factor = factor\n self.calls = 0\n\ndouble = Multiplier(2)\nprint(double.factor)\n```\n\n```output\n2\n```\n:::\n\n:::cell\n`__call__` makes the instance usable with function-call syntax.\n\n```python\nclass Multiplier:\n def __init__(self, factor):\n self.factor = factor\n self.calls = 0\n\n def __call__(self, value):\n self.calls += 1\n return value * self.factor\n\ndouble = Multiplier(2)\nprint(double(5))\nprint(double(7))\n```\n\n```output\n10\n14\n```\n:::\n\n:::cell\nBecause the callable is still an object, it can remember state across calls.\n\n```python\nprint(double.calls)\n```\n\n```output\n2\n```\n:::\n\n:::note\n- `callable(obj)` checks whether an object can be called.\n- Callable objects are good for named, stateful behavior.\n- Prefer plain functions when no instance state is needed.\n:::\n', 'callable-types.md': '+++\nslug = "callable-types"\ntitle = "Callable Types"\nsection = "Types"\nsummary = "Callable annotations describe functions passed as values."\ndoc_path = "/library/typing.html#annotating-callable-objects"\nsee_also = [\n "functions",\n "callable-objects",\n "protocols",\n]\n+++\n\nCallable annotations describe values that can be called like functions. They are useful when a function accepts a callback, strategy, predicate, or transformation.\n\n`Callable[[int], int]` says how the callback will be called: one integer argument, integer result. The annotation helps tools and readers, while runtime still only needs an object that is actually callable.\n\nUse `Callable` for simple call shapes. Use a protocol when the callback needs named attributes, overloaded signatures, or a more descriptive interface.\n\n:::program\n```python\nfrom collections.abc import Callable\n\n\ndef apply_twice(value: int, func: Callable[[int], int]) -> int:\n return func(func(value))\n\n\ndef add_one(number: int) -> int:\n return number + 1\n\nclass Doubler:\n def __call__(self, number: int) -> int:\n return number * 2\n\nprint(apply_twice(3, add_one))\nprint(apply_twice(3, Doubler()))\nprint(callable(add_one), callable(Doubler()))\n```\n:::\n\n:::cell\nUse `Callable[[Arg], Return]` for function-shaped values. The callback is passed in and called by the receiving function.\n\n```python\nfrom collections.abc import Callable\n\n\ndef apply_twice(value: int, func: Callable[[int], int]) -> int:\n return func(func(value))\n\n\ndef add_one(number: int) -> int:\n return number + 1\n\nprint(apply_twice(3, add_one))\n```\n\n```output\n5\n```\n:::\n\n:::cell\nCallable annotations are structural: an object with `__call__` can also satisfy the shape.\n\n```python\nclass Doubler:\n def __call__(self, number: int) -> int:\n return number * 2\n\nprint(apply_twice(3, Doubler()))\n```\n\n```output\n12\n```\n:::\n\n:::cell\nRuntime callability is a separate question from static annotation. `callable()` checks whether Python can call the object.\n\n```python\nprint(callable(add_one), callable(Doubler()))\n```\n\n```output\nTrue True\n```\n:::\n\n:::note\n- Use `Callable[[Arg], Return]` for simple function-shaped values.\n- The annotation documents how the callback will be called.\n- For complex call signatures, protocols can be clearer.\n:::\n', 'casts-and-any.md': '+++\nslug = "casts-and-any"\ntitle = "Casts and Any"\nsection = "Types"\nsummary = "Any and cast are escape hatches for places static analysis cannot prove."\ndoc_path = "/library/typing.html#typing.cast"\nsee_also = [\n "type-hints",\n "runtime-type-checks",\n "typed-dicts",\n]\n+++\n\n`Any` and `cast()` are escape hatches. They are useful at messy boundaries where a type checker cannot prove what a value is, but they also remove protection when overused.\n\n`Any` tells static tools to stop checking most operations on a value. `cast(T, value)` tells the type checker to treat a value as `T`, but it returns the same runtime object unchanged.\n\nPrefer narrowing with runtime checks when possible. Use `cast()` when another invariant already proves the type and the checker cannot see that proof.\n\n:::program\n```python\nfrom typing import Any, cast\n\nraw: Any = {"score": "98"}\nscore_text = cast(dict[str, str], raw)["score"]\nscore = int(score_text)\n\nprint(score + 2)\nprint(cast(list[int], raw) is raw)\nprint(type(raw).__name__)\n```\n:::\n\n:::cell\n`Any` disables most static checking for a value. The runtime object is still whatever value was actually assigned.\n\n```python\nfrom typing import Any, cast\n\nraw: Any = {"score": "98"}\nscore_text = cast(dict[str, str], raw)["score"]\nscore = int(score_text)\nprint(score + 2)\n```\n\n```output\n100\n```\n:::\n\n:::cell\n`cast()` does not convert or validate the value. It returns the same object at runtime.\n\n```python\nprint(cast(list[int], raw) is raw)\nprint(type(raw).__name__)\n```\n\n```output\nTrue\ndict\n```\n:::\n\n:::cell\nA real runtime check narrows by inspecting the value. This is safer when the input is untrusted.\n\n```python\nvalue: object = {"score": "98"}\nif isinstance(value, dict):\n print(value["score"])\n```\n\n```output\n98\n```\n:::\n\n:::note\n- `Any` disables most static checking for a value.\n- `cast()` tells the type checker to trust you without changing the runtime object.\n- Prefer narrowing with checks when possible.\n:::\n', 'classes.md': '+++\nslug = "classes"\ntitle = "Classes"\nsection = "Classes"\nsummary = "Classes bundle data and behavior into new object types."\ndoc_path = "/tutorial/classes.html"\n+++\n\nClasses define new object types by bundling data with behavior. They are useful when several values and operations belong together and should travel as one object.\n\nThe alternative is often a dictionary plus separate functions. That is fine for loose data, but a class gives the data a stable API and keeps behavior next to the state it changes.\n\n`__init__` initializes each instance, and methods receive the instance as `self`. Separate instances keep separate state because each object has its own attributes.\n\n:::program\n```python\nclass Counter:\n def __init__(self, start=0):\n self.value = start\n\n def increment(self, amount=1):\n self.value += amount\n return self.value\n\nfirst = Counter()\nsecond = Counter(10)\n\nprint(first.value)\nprint(second.value)\nprint(first.increment())\nprint(second.increment(5))\n```\n:::\n\n:::cell\nDefine a class when data and behavior should travel together. The initializer gives each object its starting state.\n\n```python\nclass Counter:\n def __init__(self, start=0):\n self.value = start\n\nfirst = Counter()\nsecond = Counter(10)\nprint(first.value)\nprint(second.value)\n```\n\n```output\n0\n10\n```\n:::\n\n:::cell\nMethods are functions attached to the class. `self` is the particular object receiving the method call, so separate instances keep separate state.\n\n```python\nclass Counter:\n def __init__(self, start=0):\n self.value = start\n\n def increment(self, amount=1):\n self.value += amount\n return self.value\n\nfirst = Counter()\nsecond = Counter(10)\nprint(first.increment())\nprint(second.increment(5))\n```\n\n```output\n1\n15\n```\n:::\n\n:::note\n- `self` is the instance the method is operating on.\n- `__init__` initializes each new object.\n- Use classes when behavior belongs with state; use dictionaries for looser structured data.\n- Instance attributes belong to one object, not to the class as a whole.\n:::\n', 'closures.md': '+++\nslug = "closures"\ntitle = "Closures"\nsection = "Functions"\nsummary = "Inner functions can remember values from an enclosing scope."\ndoc_path = "/reference/executionmodel.html#binding-of-names"\n+++\n\nA closure is a function that remembers names from the scope where it was created. This lets you configure behavior once and call it later.\n\nEach call to the outer function creates a separate remembered environment. That is why `double` and `triple` can share the same code but keep different factors.\n\nClosures are a foundation for decorators, callbacks, and small function factories.\n\n:::program\n```python\ndef make_multiplier(factor):\n def multiply(value):\n return value * factor\n return multiply\n\ndouble = make_multiplier(2)\nprint(double(5))\n\ntriple = make_multiplier(3)\nprint(triple(5))\n```\n:::\n\n:::cell\nDefine a function inside another function when the inner behavior needs to remember setup from the outer call. The returned function keeps access to `factor`.\n\n```python\ndef make_multiplier(factor):\n def multiply(value):\n return value * factor\n return multiply\n\ndouble = make_multiplier(2)\nprint(double(5))\n```\n\n```output\n10\n```\n:::\n\n:::cell\nCalling the outer function again creates a separate closure. `triple` uses the same inner code, but remembers a different `factor`.\n\n```python\ntriple = make_multiplier(3)\nprint(triple(5))\n```\n\n```output\n15\n```\n:::\n\n:::note\n- A closure keeps access to names from the scope where the inner function was created.\n- Each call to the outer function can create a separate remembered environment.\n- Closures are useful for callbacks, small factories, and decorators.\n:::\n', 'collections-module.md': '+++\nslug = "collections-module"\ntitle = "Collections Module"\nsection = "Collections"\nsummary = "collections provides specialized containers for common data shapes."\ndoc_path = "/library/collections.html"\n+++\n\ncollections provides specialized containers for common data shapes. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.\n\nUse it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.\n\nThe example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.\n\n:::program\n```python\nfrom collections import Counter, defaultdict, deque, namedtuple\n\ncounts = Counter("banana")\nprint(counts.most_common(2))\n\ngroups = defaultdict(list)\nfor name, team in [("Ada", "red"), ("Grace", "blue"), ("Lin", "red")]:\n groups[team].append(name)\nprint(dict(groups))\n\nqueue = deque(["first"])\nqueue.append("second")\nprint(queue.popleft())\n\nPoint = namedtuple("Point", "x y")\nprint(Point(2, 3).x)\n```\n:::\n\n:::cell\nUse `Counter` when counting is the data shape.\n\n```python\nfrom collections import Counter, defaultdict, deque, namedtuple\n\ncounts = Counter("banana")\nprint(counts.most_common(2))\n\ngroups = defaultdict(list)\nfor name, team in [("Ada", "red"), ("Grace", "blue"), ("Lin", "red")]:\n groups[team].append(name)\nprint(dict(groups))\n\nqueue = deque(["first"])\nqueue.append("second")\nprint(queue.popleft())\n\nPoint = namedtuple("Point", "x y")\nprint(Point(2, 3).x)\n```\n\n```output\n[(\'a\', 3), (\'n\', 2)]\n{\'red\': [\'Ada\', \'Lin\'], \'blue\': [\'Grace\']}\nfirst\n2\n```\n:::\n\n:::note\n- Use `Counter` when counting is the data shape.\n- Use `defaultdict` when grouping values by key.\n- Use `deque` for efficient queue operations and `namedtuple` for lightweight named records.\n:::\n', 'comprehension-patterns.md': '+++\nslug = "comprehension-patterns"\ntitle = "Comprehension Patterns"\nsection = "Collections"\nsummary = "Comprehensions can use multiple for clauses and filters when the shape stays clear."\ndoc_path = "/tutorial/datastructures.html#list-comprehensions"\nsee_also = [\n "comprehensions",\n "generator-expressions",\n "for-loops",\n]\n+++\n\nComprehensions can contain more than one `for` clause and more than one `if` filter. The clauses are read in the same order as nested loops.\n\nUse these forms only while the shape remains easy to scan. If a comprehension starts needing several names, comments, or branches, an explicit loop is usually better.\n\nNested comprehensions build concrete collections immediately, just like simpler list, dict, and set comprehensions.\n\n:::program\n```python\ncolors = ["red", "blue"]\nsizes = ["S", "M"]\nvariants = [(color, size) for color in colors for size in sizes]\nprint(variants)\n\nnumbers = range(10)\nfiltered = [n for n in numbers if n % 2 == 0 if n > 2]\nprint(filtered)\n```\n:::\n\n:::cell\nMultiple `for` clauses behave like nested loops. The leftmost `for` is the outer loop, and the next `for` runs inside it.\n\n```python\ncolors = ["red", "blue"]\nsizes = ["S", "M"]\nvariants = [(color, size) for color in colors for size in sizes]\nprint(variants)\n```\n\n```output\n[(\'red\', \'S\'), (\'red\', \'M\'), (\'blue\', \'S\'), (\'blue\', \'M\')]\n```\n:::\n\n:::cell\nMultiple `if` clauses filter values. They are useful for simple conditions, but an explicit loop is clearer when the rules need names or explanation.\n\n```python\nnumbers = range(10)\nfiltered = [n for n in numbers if n % 2 == 0 if n > 2]\nprint(filtered)\n```\n\n```output\n[4, 6, 8]\n```\n:::\n\n:::note\n- Read comprehension clauses from left to right.\n- Multiple `for` clauses act like nested loops.\n- Prefer an explicit loop when the comprehension stops being obvious.\n:::\n', 'comprehensions.md': '+++\nslug = "comprehensions"\ntitle = "Comprehensions"\nsection = "Collections"\nsummary = "Comprehensions build collections by mapping and filtering iterables."\ndoc_path = "/tutorial/datastructures.html#list-comprehensions"\n+++\n\nComprehensions are expression forms for building concrete collections from iterables. Read them from left to right: produce this value, for each item, optionally only when a condition is true.\n\nThey are best for direct transformations where the expression is still easy to scan. When the work needs several statements or names, an explicit loop is usually clearer.\n\nList, dictionary, and set comprehensions are eager: they build collections immediately. Generator expressions use similar syntax to stream values later and are covered in the Iteration section.\n\n:::program\n```python\nnames = ["ada", "guido", "grace"]\ntitled = [name.title() for name in names]\nprint(titled)\n\nscores = {"Ada": 10, "Guido": 8, "Grace": 10}\nhigh_scores = {name: score for name, score in scores.items() if score >= 10}\nprint(high_scores)\n\nunique_scores = {score for score in scores.values()}\nprint(unique_scores)\n```\n:::\n\n:::cell\nA list comprehension maps each input item to one output item. This one calls `title()` for every name and collects the results in a new list.\n\n```python\nnames = ["ada", "guido", "grace"]\ntitled = [name.title() for name in names]\nprint(titled)\n```\n\n```output\n[\'Ada\', \'Guido\', \'Grace\']\n```\n:::\n\n:::cell\nAdd an `if` clause when only some items should appear. A dictionary comprehension can transform key/value pairs while preserving the dictionary shape.\n\n```python\nscores = {"Ada": 10, "Guido": 8, "Grace": 10}\nhigh_scores = {name: score for name, score in scores.items() if score >= 10}\nprint(high_scores)\n```\n\n```output\n{\'Ada\': 10, \'Grace\': 10}\n```\n:::\n\n:::cell\nA set comprehension keeps only unique results. Here two people have the same score, so the resulting set has two values.\n\n```python\nunique_scores = {score for score in scores.values()}\nprint(unique_scores)\n```\n\n```output\n{8, 10}\n```\n:::\n\n:::note\n- The left side says what to produce; the `for` clause says where values come from.\n- Use an `if` clause for simple filters.\n- List, dict, and set comprehensions build concrete collections immediately.\n- Switch to a loop when the transformation needs multiple steps or explanations.\n:::\n', 'conditionals.md': '+++\nslug = "conditionals"\ntitle = "Conditionals"\nsection = "Control Flow"\nsummary = "if, elif, and else choose which block runs."\ndoc_path = "/tutorial/controlflow.html#if-statements"\n+++\n\n`if`, `elif`, and `else` let a program choose one path based on a condition. Python uses indentation to show which statements belong to each branch.\n\nConditions use Python truthiness: booleans work directly, and many objects such as empty lists or empty strings are considered false. Order branches from most specific to most general.\n\nUse `elif` to keep one decision flat instead of nested. Use Python\'s ternary expression only when you are choosing between two values.\n\n:::program\n```python\ntemperature = 72\n\nif temperature < 60:\n print("cold")\nelif temperature < 80:\n print("comfortable")\nelse:\n print("hot")\n\nitems = ["coat", "hat"]\nif items:\n print(f"packing {len(items)} items")\n\nstatus = "ok" if temperature < 90 else "danger"\nprint(status)\n```\n:::\n\n:::cell\nStart with the value that the branches will test. A conditional is only useful when the branch condition is visible and meaningful.\n\nUse `if`, `elif`, and `else` for one ordered choice. Python tests the branches from top to bottom and runs only the first matching block.\n\n```python\ntemperature = 72\n\nif temperature < 60:\n print("cold")\nelif temperature < 80:\n print("comfortable")\nelse:\n print("hot")\n```\n\n```output\ncomfortable\n```\n:::\n\n:::cell\nTruthiness is part of conditional flow. Empty collections are false, so `if items:` reads as “if there is anything to work with.”\n\n```python\nitems = ["coat", "hat"]\nif items:\n print(f"packing {len(items)} items")\n```\n\n```output\npacking 2 items\n```\n:::\n\n:::cell\nUse the ternary expression when you are choosing a value. If either side needs multiple statements, use a normal `if` block instead.\n\n```python\nstatus = "ok" if temperature < 90 else "danger"\nprint(status)\n```\n\n```output\nok\n```\n:::\n\n:::note\n- Python has no mandatory parentheses around conditions; the colon and indentation define the block.\n- Comparison operators such as `<` and `==` can be chained, as in `0 < value < 10`.\n- Keep branch bodies short; move larger work into functions so the decision remains easy to scan.\n:::\n', 'constants.md': '+++\nslug = "constants"\ntitle = "Constants"\nsection = "Basics"\nsummary = "Python uses naming conventions for values that should not change."\ndoc_path = "/tutorial/classes.html#python-scopes-and-namespaces"\n+++\n\nPython has no `const` keyword for ordinary variables. Instead, modules use all-caps names to mark values that should be treated as constants by convention.\n\nThe interpreter will not stop rebinding, but the convention is important API communication. Readers understand that `MAX_RETRIES` is configuration, not loop state.\n\nNamed constants remove magic values from code and give repeated literals one place to change.\n\n:::program\n```python\nMAX_RETRIES = 3\nAPI_VERSION = "2026-05"\n\nfor attempt in range(1, MAX_RETRIES + 1):\n print(f"attempt {attempt} of {MAX_RETRIES}")\n\nprint(API_VERSION)\n```\n:::\n\n:::cell\nPython does not have a `const` declaration like Go or Rust. Instead, modules use all-caps names for values callers should treat as fixed.\n\nThe interpreter will still let you rebind the name, but the convention is strong enough that readers understand the design intent.\n\n```python\nMAX_RETRIES = 3\nAPI_VERSION = "2026-05"\n\nfor attempt in range(1, MAX_RETRIES + 1):\n print(f"attempt {attempt} of {MAX_RETRIES}")\n```\n\n```output\nattempt 1 of 3\nattempt 2 of 3\nattempt 3 of 3\n```\n:::\n\n:::cell\nConstants are useful for configuration values that should be named once and reused instead of repeated as magic literals.\n\n```python\nprint(API_VERSION)\n```\n\n```output\n2026-05\n```\n:::\n\n:::note\n- Python has no `const` keyword for ordinary names.\n- All-caps names such as `MAX_RETRIES` communicate that a value is intended to stay fixed.\n:::\n', 'container-protocols.md': '+++\nslug = "container-protocols"\ntitle = "Container Protocols"\nsection = "Data Model"\nsummary = "Container methods connect objects to indexing, membership, and item assignment."\ndoc_path = "/reference/datamodel.html#emulating-container-types"\nsee_also = [\n "lists",\n "dicts",\n "special-methods",\n]\n+++\n\nContainer protocols let a class behave like the collection it represents. Instead of inventing method names such as `has()` or `lookup()`, the object can support `in`, indexing, and assignment.\n\nThe key methods are small and familiar: `__contains__` powers `in`, `__getitem__` powers `obj[key]`, and `__setitem__` powers `obj[key] = value`. Add only the operations the object can honestly support.\n\nThis keeps the public interface aligned with Python\'s built-in containers. Callers can use the same syntax for custom records, caches, tables, and sequence-like objects.\n\n:::program\n```python\nclass Scores:\n def __init__(self):\n self._scores = {}\n\n def __contains__(self, name):\n return name in self._scores\n\n def __getitem__(self, name):\n return self._scores[name]\n\n def __setitem__(self, name, score):\n self._scores[name] = score\n\nscores = Scores()\nscores["Ada"] = 98\nprint("Ada" in scores)\nprint(scores["Ada"])\n```\n:::\n\n:::cell\n`__setitem__` gives assignment syntax to a custom container.\n\n```python\nclass Scores:\n def __init__(self):\n self._scores = {}\n\n def __setitem__(self, name, score):\n self._scores[name] = score\n\nscores = Scores()\nscores["Ada"] = 98\nprint(scores._scores)\n```\n\n```output\n{\'Ada\': 98}\n```\n:::\n\n:::cell\n`__contains__` answers membership tests written with `in`.\n\n```python\nclass Scores:\n def __init__(self):\n self._scores = {"Ada": 98}\n\n def __contains__(self, name):\n return name in self._scores\n\nscores = Scores()\nprint("Ada" in scores)\n```\n\n```output\nTrue\n```\n:::\n\n:::cell\n`__getitem__` connects bracket lookup to your internal storage.\n\n```python\nclass Scores:\n def __init__(self):\n self._scores = {"Ada": 98}\n\n def __getitem__(self, name):\n return self._scores[name]\n\nscores = Scores()\nprint(scores["Ada"])\n```\n\n```output\n98\n```\n:::\n\n:::note\n- Implement the narrowest container protocol your object needs.\n- Use `KeyError` and `IndexError` consistently with built-in containers.\n- If a plain `dict` or `list` is enough, prefer it over a custom container.\n:::\n', 'context-managers.md': '+++\nslug = "context-managers"\ntitle = "Context Managers"\nsection = "Data Model"\nsummary = "with ensures setup and cleanup happen together."\ndoc_path = "/reference/datamodel.html#context-managers"\nsee_also = [\n "exceptions",\n "special-methods",\n "descriptors",\n]\n+++\n\nContext managers define setup and cleanup around a block of code. The `with` statement guarantees that cleanup runs when the block exits, even when an exception is raised.\n\nThe protocol is powered by `__enter__` and `__exit__`. The `contextlib.contextmanager` decorator is a concise way to write the same idea as a generator when a full class would be noisy.\n\nProduction code often uses `with` for files, locks, transactions, temporary state, and resources that need reliable release.\n\n:::program\n```python\nfrom contextlib import contextmanager\n\nclass Tag:\n def __init__(self, name):\n self.name = name\n\n def __enter__(self):\n print(f"<{self.name}>")\n return self\n\n def __exit__(self, exc_type, exc, tb):\n print(f"{self.name}>")\n return False\n\n@contextmanager\ndef tag(name):\n print(f"<{name}>")\n try:\n yield\n finally:\n print(f"{name}>")\n\nwith Tag("section"):\n print("content")\n\ntry:\n with tag("error"):\n raise ValueError("boom")\nexcept ValueError:\n print("handled")\n```\n:::\n\n:::cell\nA class-based context manager implements `__enter__` and `__exit__`. The value returned by `__enter__` is bound by `as` when the `with` statement uses it.\n\n```python\nclass Tag:\n def __init__(self, name):\n self.name = name\n\n def __enter__(self):\n print(f"<{self.name}>")\n return self\n\n def __exit__(self, exc_type, exc, tb):\n print(f"{self.name}>")\n return False\n\nwith Tag("section"):\n print("content")\n```\n\n```output\n\n```\n:::\n\n:::cell\n`contextlib.contextmanager` writes the same setup/cleanup shape as a generator. Code before `yield` is setup, and code after `yield` is cleanup.\n\n```python\nfrom contextlib import contextmanager\n\n@contextmanager\ndef tag(name):\n print(f"<{name}>")\n try:\n yield\n finally:\n print(f"{name}>")\n\nwith tag("note"):\n print("body")\n```\n\n```output\n\nbody\n\n```\n:::\n\n:::cell\nCleanup still runs when the block raises. Returning `False` from `__exit__`, or letting a generator context manager re-raise, allows the exception to keep propagating.\n\n```python\ntry:\n with tag("error"):\n raise ValueError("boom")\nexcept ValueError:\n print("handled")\n```\n\n```output\n\n\nhandled\n```\n:::\n\n:::note\n- Files, locks, and temporary state commonly use context managers.\n- `__enter__` and `__exit__` power the protocol.\n- Use `finally` when cleanup must happen after errors too.\n- Returning true from `__exit__` suppresses an exception; do that only intentionally.\n:::\n', 'copying-collections.md': '+++\nslug = "copying-collections"\ntitle = "Copying Collections"\nsection = "Collections"\nsummary = "Copies can duplicate a container while still sharing nested objects."\ndoc_path = "/library/copy.html"\n+++\n\nCopies can duplicate a container while still sharing nested objects. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.\n\nUse it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.\n\nThe example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.\n\n:::program\n```python\nimport copy\n\nrows = [["Ada"], ["Grace"]]\nshallow = rows.copy()\ndeep = copy.deepcopy(rows)\n\nrows[0].append("Lovelace")\n\nprint(shallow)\nprint(deep)\nprint(rows[0] is shallow[0])\nprint(rows[0] is deep[0])\n```\n:::\n\n:::cell\nA shallow copy makes a new outer container.\n\n```python\nimport copy\n\nrows = [["Ada"], ["Grace"]]\nshallow = rows.copy()\ndeep = copy.deepcopy(rows)\n\nrows[0].append("Lovelace")\n\nprint(shallow)\nprint(deep)\nprint(rows[0] is shallow[0])\nprint(rows[0] is deep[0])\n```\n\n```output\n[[\'Ada\', \'Lovelace\'], [\'Grace\']]\n[[\'Ada\'], [\'Grace\']]\nTrue\nFalse\n```\n:::\n\n:::note\n- A shallow copy makes a new outer container.\n- Nested objects are still shared by a shallow copy.\n- Use `copy.deepcopy()` only when nested independence is required.\n:::\n', 'csv-data.md': '+++\nslug = "csv-data"\ntitle = "CSV Data"\nsection = "Standard Library"\nsummary = "csv reads and writes row-shaped text data."\ndoc_path = "/library/csv.html"\n+++\n\ncsv reads and writes row-shaped text data. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.\n\nUse it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.\n\nThe example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.\n\n:::program\n```python\nimport csv\nimport io\n\ntext = "name,score\\nAda,98\\nGrace,95\\n"\nreader = csv.DictReader(io.StringIO(text))\nrows = list(reader)\n\nprint(rows[0]["name"])\nprint(sum(int(row["score"]) for row in rows))\n\noutput = io.StringIO()\nwriter = csv.DictWriter(output, fieldnames=["name", "passed"])\nwriter.writeheader()\nwriter.writerow({"name": "Ada", "passed": True})\nprint(output.getvalue().splitlines()[1])\n```\n:::\n\n:::cell\nUse `DictReader` when column names should become dictionary keys.\n\n```python\nimport csv\nimport io\n\ntext = "name,score\\nAda,98\\nGrace,95\\n"\nreader = csv.DictReader(io.StringIO(text))\nrows = list(reader)\n\nprint(rows[0]["name"])\nprint(sum(int(row["score"]) for row in rows))\n\noutput = io.StringIO()\nwriter = csv.DictWriter(output, fieldnames=["name", "passed"])\nwriter.writeheader()\nwriter.writerow({"name": "Ada", "passed": True})\nprint(output.getvalue().splitlines()[1])\n```\n\n```output\nAda\n193\nAda,True\n```\n:::\n\n:::note\n- Use `DictReader` when column names should become dictionary keys.\n- CSV fields arrive as text, so convert numbers explicitly.\n- `DictWriter` writes dictionaries back to row-shaped text.\n:::\n', 'custom-exceptions.md': '+++\nslug = "custom-exceptions"\ntitle = "Custom Exceptions"\nsection = "Errors"\nsummary = "Custom exception classes name failures that belong to your domain."\ndoc_path = "/tutorial/errors.html#user-defined-exceptions"\n+++\n\nCustom exceptions give names to failures in your problem domain. A named exception is easier to catch and explain than a generic error with only a string message.\n\nRaise the custom exception at the point where the invalid state is discovered. Include a message for the specific occurrence.\n\nCatch custom exceptions at the boundary where recovery makes sense, such as returning an error response or asking for corrected input.\n\n:::program\n```python\nclass EmptyCartError(Exception):\n pass\n\nprint(EmptyCartError.__name__)\n\n\ndef checkout(items):\n if not items:\n raise EmptyCartError("cart is empty")\n return "paid"\n\nprint(checkout(["book"]))\n\ntry:\n checkout([])\nexcept EmptyCartError as error:\n print(error)\n```\n:::\n\n:::cell\nCreate a custom exception when a failure has a name in your problem domain. The class can be empty at first.\n\n```python\nclass EmptyCartError(Exception):\n pass\n\nprint(EmptyCartError.__name__)\n```\n\n```output\nEmptyCartError\n```\n:::\n\n:::cell\nRaise the custom exception where the invalid state is detected. Normal inputs still follow the ordinary success path.\n\n```python\ndef checkout(items):\n if not items:\n raise EmptyCartError("cart is empty")\n return "paid"\n\nprint(checkout(["book"]))\n```\n\n```output\npaid\n```\n:::\n\n:::cell\nCallers can catch the precise error type without accidentally catching unrelated failures.\n\n```python\ntry:\n checkout([])\nexcept EmptyCartError as error:\n print(error)\n```\n\n```output\ncart is empty\n```\n:::\n\n:::note\n- Subclass `Exception` for errors callers are expected to catch.\n- A custom exception name can be clearer than reusing a generic `ValueError` everywhere.\n- Catch custom exceptions at a boundary that can recover or report clearly.\n:::\n', 'dataclasses.md': '+++\nslug = "dataclasses"\ntitle = "Dataclasses"\nsection = "Classes"\nsummary = "dataclass generates common class methods for data containers."\ndoc_path = "/library/dataclasses.html"\n+++\n\n`dataclass` is a standard-library decorator for classes that mainly store data. It generates methods such as `__init__` and `__repr__` from type-annotated fields.\n\nDataclasses reduce boilerplate while keeping classes explicit. They are a good fit for simple records, configuration objects, and values passed between layers.\n\nType annotations define fields. Defaults work like normal class attributes and appear in the generated initializer.\n\n:::program\n```python\nfrom dataclasses import dataclass\n\n@dataclass\nclass User:\n name: str\n active: bool = True\n\nuser = User("Ada")\nprint(user)\nprint(user.name)\n\ninactive = User("Guido", active=False)\nprint(inactive)\nprint(inactive.active)\n```\n:::\n\n:::cell\nA dataclass uses annotations to define fields. Python generates an initializer, so the class can be constructed without writing `__init__` by hand.\n\n```python\nfrom dataclasses import dataclass\n\n@dataclass\nclass User:\n name: str\n active: bool = True\n\nuser = User("Ada")\nprint(user)\n```\n\n```output\nUser(name=\'Ada\', active=True)\n```\n:::\n\n:::cell\nThe generated instance still exposes ordinary attributes. A dataclass is a regular class with useful methods filled in.\n\n```python\nprint(user.name)\n```\n\n```output\nAda\n```\n:::\n\n:::cell\nDefaults can be overridden by keyword. The generated representation includes the field names, which is useful during debugging.\n\n```python\ninactive = User("Guido", active=False)\nprint(inactive)\nprint(inactive.active)\n```\n\n```output\nUser(name=\'Guido\', active=False)\nFalse\n```\n:::\n\n:::note\n- Type annotations define dataclass fields.\n- Dataclasses generate methods but remain normal Python classes.\n- Use `field()` for advanced defaults such as per-instance lists or dictionaries.\n:::\n', 'datetime.md': '+++\nslug = "datetime"\ntitle = "Dates and Times"\nsection = "Standard Library"\nsummary = "datetime represents dates, times, durations, formatting, and parsing."\ndoc_path = "/library/datetime.html"\n+++\n\nThe `datetime` module covers several related ideas: `date` for calendar days, `time` for clock times, `datetime` for both together, and `timedelta` for durations.\n\nTimezone-aware datetimes avoid ambiguity in real systems. `timezone.utc` is a clear default for examples because output stays stable and portable.\n\nUse ISO formatting for interchange, `strftime()` for display, and parsing helpers such as `fromisoformat()` to turn text back into datetime objects.\n\n:::program\n```python\nfrom datetime import date, datetime, time, timedelta, timezone\n\nrelease_day = date(2026, 5, 4)\nmeeting_time = time(12, 30)\ncreated_at = datetime.combine(release_day, meeting_time, tzinfo=timezone.utc)\n\nprint(release_day.isoformat())\nprint(meeting_time.isoformat())\nprint(created_at.isoformat())\n\nexpires_at = created_at + timedelta(days=7, hours=2)\nprint(expires_at.isoformat())\n\nprint(created_at.strftime("%Y-%m-%d %H:%M %Z"))\nparsed = datetime.fromisoformat("2026-05-04T12:30:00+00:00")\nprint(parsed == created_at)\n```\n:::\n\n:::cell\nThe `datetime` module separates calendar dates, clock times, combined datetimes, and durations. Import the types you need explicitly.\n\nUse `date` for a calendar day and `time` for a time of day. Combine them into a timezone-aware `datetime` when you mean an instant.\n\n`isoformat()` produces stable machine-readable text. It is a good default for examples, APIs, and logs.\n\n```python\nfrom datetime import date, datetime, time, timedelta, timezone\n\nrelease_day = date(2026, 5, 4)\nmeeting_time = time(12, 30)\ncreated_at = datetime.combine(release_day, meeting_time, tzinfo=timezone.utc)\n\nprint(release_day.isoformat())\nprint(meeting_time.isoformat())\nprint(created_at.isoformat())\n```\n\n```output\n2026-05-04\n12:30:00\n2026-05-04T12:30:00+00:00\n```\n:::\n\n:::cell\nUse `timedelta` for durations. Adding one to a `datetime` produces another `datetime` without manually changing calendar fields.\n\n```python\nexpires_at = created_at + timedelta(days=7, hours=2)\nprint(expires_at.isoformat())\n```\n\n```output\n2026-05-11T14:30:00+00:00\n```\n:::\n\n:::cell\nUse `strftime()` for human-facing formatting and `fromisoformat()` when reading ISO 8601 text back into a `datetime`.\n\n```python\nprint(created_at.strftime("%Y-%m-%d %H:%M %Z"))\nparsed = datetime.fromisoformat("2026-05-04T12:30:00+00:00")\nprint(parsed == created_at)\n```\n\n```output\n2026-05-04 12:30 UTC\nTrue\n```\n:::\n\n:::note\n- Use timezone-aware datetimes for instants that cross system or user boundaries.\n- Use `date` for calendar days, `time` for clock times, `datetime` for both, and `timedelta` for durations.\n- Prefer ISO 8601 strings for interchange; use `strftime` for human-facing display.\n:::\n', 'decorators.md': '+++\nslug = "decorators"\ntitle = "Decorators"\nsection = "Functions"\nsummary = "Decorators wrap or register functions using @ syntax."\ndoc_path = "/glossary.html#term-decorator"\nsee_also = [\n "closures",\n "functions",\n "callable-types",\n]\n+++\n\nA decorator is a callable that receives a function and returns a replacement. The `@` syntax applies that transformation at function definition time.\n\nDecorators are common in frameworks because they can register handlers or add behavior while keeping the decorated function focused on the core action.\n\n`@decorator` is shorthand for rebinding a function to the decorator\'s return value. Production wrappers usually use `functools.wraps` so debugging, help text, and framework introspection still see the original function metadata.\n\n:::program\n```python\nfrom functools import wraps\n\n\ndef loud(func):\n @wraps(func)\n def wrapper(name):\n return func(name).upper()\n return wrapper\n\n\ndef greet(name):\n return f"hello {name}"\n\nmanual_greet = loud(greet)\nprint(manual_greet("python"))\n\n@loud\ndef welcome(name):\n """Return a welcome message."""\n return f"welcome {name}"\n\nprint(welcome("workers"))\nprint(welcome.__name__)\n```\n:::\n\n:::cell\nA decorator is just a function that takes a function and returns another callable. Applying it manually shows the wrapping step.\n\n```python\nfrom functools import wraps\n\n\ndef loud(func):\n @wraps(func)\n def wrapper(name):\n return func(name).upper()\n return wrapper\n\n\ndef greet(name):\n return f"hello {name}"\n\nmanual_greet = loud(greet)\nprint(manual_greet("python"))\n```\n\n```output\nHELLO PYTHON\n```\n:::\n\n:::cell\nThe `@loud` syntax performs the same rebinding at definition time. After decoration, `welcome` refers to the wrapper returned by `loud`.\n\n```python\n@loud\ndef welcome(name):\n """Return a welcome message."""\n return f"welcome {name}"\n\nprint(welcome("workers"))\n```\n\n```output\nWELCOME WORKERS\n```\n:::\n\n:::cell\n`functools.wraps` copies useful metadata from the original function onto the wrapper.\n\n```python\nprint(welcome.__name__)\nprint(welcome.__doc__)\n```\n\n```output\nwelcome\nReturn a welcome message.\n```\n:::\n\n:::note\n- `@decorator` is shorthand for assigning `func = decorator(func)`.\n- Decorators can wrap, replace, or register functions.\n- Use `functools.wraps` in production wrappers that should preserve metadata.\n:::\n', 'delete-statements.md': '+++\nslug = "delete-statements"\ntitle = "Delete Statements"\nsection = "Data Model"\nsummary = "del removes bindings, items, and attributes rather than producing a value."\ndoc_path = "/reference/simple_stmts.html#the-del-statement"\nsee_also = [\n "variables",\n "dicts",\n "mutability",\n]\n+++\n\n`del` removes a binding or an item. It is a statement, not a function, and it does not return the removed value.\n\nUse `del name` when a name should no longer be bound. Use `del mapping[key]` or `del sequence[index]` when mutating a container by removing one part.\n\nThis is different from assigning `None`: `None` is still a value, while `del` removes the binding or slot.\n\n:::program\n```python\nprofile = {"name": "Ada", "temporary": True}\ndel profile["temporary"]\nprint(profile)\n\nitems = ["a", "b", "c"]\ndel items[1]\nprint(items)\n\nvalue = "cached"\ndel value\nprint("value" in locals())\n```\n:::\n\n:::cell\nDeleting a dictionary key mutates the dictionary. The key is gone; it has not been set to `None`.\n\n```python\nprofile = {"name": "Ada", "temporary": True}\ndel profile["temporary"]\nprint(profile)\n```\n\n```output\n{\'name\': \'Ada\'}\n```\n:::\n\n:::cell\nDeleting a list item removes that position and shifts later items left.\n\n```python\nitems = ["a", "b", "c"]\ndel items[1]\nprint(items)\n```\n\n```output\n[\'a\', \'c\']\n```\n:::\n\n:::cell\nDeleting a name removes the binding from the current namespace. It is different from rebinding the name to `None`.\n\n```python\nvalue = "cached"\ndel value\nprint("value" in locals())\n```\n\n```output\nFalse\n```\n:::\n\n:::note\n- `del` removes bindings or container entries.\n- Assign `None` when absence should remain an explicit value.\n- Use container methods such as `pop()` when you need the removed value back.\n:::\n', 'descriptors.md': '+++\nslug = "descriptors"\ntitle = "Descriptors"\nsection = "Data Model"\nsummary = "Descriptors customize attribute access through __get__, __set__, or __delete__."\ndoc_path = "/howto/descriptor.html"\n+++\n\nDescriptors customize attribute access through __get__, __set__, or __delete__. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.\n\nUse it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.\n\nThe example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.\n\n:::program\n```python\nclass Positive:\n def __set_name__(self, owner, name):\n self.private_name = "_" + name\n\n def __get__(self, obj, owner):\n return getattr(obj, self.private_name)\n\n def __set__(self, obj, value):\n if value <= 0:\n raise ValueError("must be positive")\n setattr(obj, self.private_name, value)\n\nclass Product:\n price = Positive()\n\n def __init__(self, price):\n self.price = price\n\nitem = Product(10)\nprint(item.price)\ntry:\n item.price = -1\nexcept ValueError as error:\n print(error)\n```\n:::\n\n:::cell\nA descriptor object lives on the class.\n\n```python\nclass Positive:\n def __set_name__(self, owner, name):\n self.private_name = "_" + name\n\n def __get__(self, obj, owner):\n return getattr(obj, self.private_name)\n\n def __set__(self, obj, value):\n if value <= 0:\n raise ValueError("must be positive")\n setattr(obj, self.private_name, value)\n\nclass Product:\n price = Positive()\n\n def __init__(self, price):\n self.price = price\n\nitem = Product(10)\nprint(item.price)\ntry:\n item.price = -1\nexcept ValueError as error:\n print(error)\n```\n\n```output\n10\nmust be positive\n```\n:::\n\n:::note\n- A descriptor object lives on the class.\n- Attribute access on instances calls descriptor methods.\n- Properties, methods, and many ORMs build on the descriptor protocol.\n:::\n', 'dicts.md': '+++\nslug = "dicts"\ntitle = "Dictionaries"\nsection = "Collections"\nsummary = "Dictionaries map keys to values for records, lookup, and structured data."\ndoc_path = "/tutorial/datastructures.html#dictionaries"\n+++\n\nDictionaries are Python\'s built-in mapping type. They exist for data where names or keys are more meaningful than numeric positions: records, lookup tables, counters, and JSON-like payloads.\n\nUse direct indexing when a key is required. Use `get()` when absence is expected and the code has a reasonable fallback.\n\nUnlike lists, dictionaries answer “what value belongs to this key?” rather than “what value is at this position?” Iterating with `items()` keeps each key next to its value.\n\n:::program\n```python\nprofile = {"name": "Ada", "language": "Python"}\nprofile["year"] = 1843\nprint(profile["name"])\nprint(profile.get("timezone", "UTC"))\n\nscores = {"Ada": 10, "Grace": 9}\nprint(scores["Grace"])\nprint(scores.get("Guido", 0))\n\nfor name, score in scores.items():\n print(f"{name}: {score}")\n```\n:::\n\n:::cell\nUse a dictionary as a small record when fields have names. Direct indexing communicates that the key is required, while `get()` communicates that a missing key has a fallback.\n\n```python\nprofile = {"name": "Ada", "language": "Python"}\nprofile["year"] = 1843\nprint(profile["name"])\nprint(profile.get("timezone", "UTC"))\n```\n\n```output\nAda\nUTC\n```\n:::\n\n:::cell\nUse a dictionary as a lookup table when keys identify values. This is different from a list, where numeric position is the lookup key.\n\n```python\nscores = {"Ada": 10, "Grace": 9}\nprint(scores["Grace"])\nprint(scores.get("Guido", 0))\n```\n\n```output\n9\n0\n```\n:::\n\n:::cell\nUse `items()` when the loop needs both keys and values. It avoids looping over keys and then indexing back into the dictionary.\n\n```python\nfor name, score in scores.items():\n print(f"{name}: {score}")\n```\n\n```output\nAda: 10\nGrace: 9\n```\n:::\n\n:::note\n- Dictionaries preserve insertion order in modern Python.\n- Use `get()` when a missing key has a reasonable default.\n- Use direct indexing when a missing key should be treated as an error.\n:::\n', 'enums.md': '+++\nslug = "enums"\ntitle = "Enums"\nsection = "Types"\nsummary = "Enum defines symbolic names for a fixed set of values."\ndoc_path = "/library/enum.html"\n+++\n\n`Enum` defines a fixed set of named values. This makes states and modes easier to read than raw strings scattered through a program.\n\nEach enum member has a name and a value. Comparing enum members is explicit and helps avoid typos that plain strings would allow.\n\nUse enums when a value must be one of a small known set: statuses, modes, directions, roles, and similar choices.\n\n:::program\n```python\nfrom enum import Enum\n\nclass Status(Enum):\n PENDING = "pending"\n DONE = "done"\n\ncurrent = Status.PENDING\nprint(current.name)\nprint(current.value)\nprint(current is Status.PENDING)\nprint(current == "pending")\n```\n:::\n\n:::cell\nAn enum member has a symbolic name and an underlying value. The symbolic name is what readers usually care about in code.\n\n```python\nfrom enum import Enum\n\nclass Status(Enum):\n PENDING = "pending"\n DONE = "done"\n\ncurrent = Status.PENDING\nprint(current.name)\nprint(current.value)\n```\n\n```output\nPENDING\npending\n```\n:::\n\n:::cell\nCompare enum members with enum members, not with raw strings. This keeps the set of valid states explicit.\n\n```python\nprint(current is Status.PENDING)\nprint(current == "pending")\n```\n\n```output\nTrue\nFalse\n```\n:::\n\n:::note\n- Enums make states and choices explicit.\n- Members have names and values.\n- Comparing enum members avoids string typo bugs.\n- Prefer raw strings for open-ended text; prefer enums for a closed set of named choices.\n:::\n', 'equality-and-identity.md': '+++\nslug = "equality-and-identity"\ntitle = "Equality and Identity"\nsection = "Data Model"\nsummary = "== compares values, while is compares object identity."\ndoc_path = "/reference/expressions.html#is-not"\n+++\n\nPython separates equality from identity. Equality asks whether two objects should be considered the same value, while identity asks whether two names point to the same object.\n\nThis distinction matters for mutable containers because two equal lists can still be independent objects. Mutating one should not imply mutating the other unless they share identity.\n\nThe `is` operator is best reserved for identity checks against singletons such as `None`. For ordinary values, `==` is the comparison readers expect.\n\n:::program\n```python\nleft = [1, 2, 3]\nright = [1, 2, 3]\nprint(left == right)\nprint(left is right)\n\nsame = left\nsame.append(4)\nprint(left)\nprint(same is left)\n\nvalue = None\nprint(value is None)\n```\n:::\n\n:::cell\nEqual containers can be different objects. `==` compares list contents, while `is` checks whether both names refer to the same list object.\n\n```python\nleft = [1, 2, 3]\nright = [1, 2, 3]\nprint(left == right)\nprint(left is right)\n```\n\n```output\nTrue\nFalse\n```\n:::\n\n:::cell\nIdentity matters when objects are mutable. `same` is another name for `left`, so mutating through one name changes the object seen through the other.\n\n```python\nsame = left\nsame.append(4)\nprint(left)\nprint(same is left)\n```\n\n```output\n[1, 2, 3, 4]\nTrue\n```\n:::\n\n:::cell\nUse `is` for singleton identity checks such as `None`. This asks whether the value is the one special `None` object.\n\n```python\nvalue = None\nprint(value is None)\n```\n\n```output\nTrue\n```\n:::\n\n:::note\n- Use `==` for ordinary value comparisons.\n- Use `is` primarily for identity checks against singletons such as `None`.\n- Equal mutable containers can still be independent objects.\n:::\n', 'exception-chaining.md': '+++\nslug = "exception-chaining"\ntitle = "Exception Chaining"\nsection = "Errors"\nsummary = "raise from preserves the original cause when translating exceptions."\ndoc_path = "/tutorial/errors.html#exception-chaining"\nsee_also = [\n "exceptions",\n "custom-exceptions",\n "assertions",\n]\n+++\n\nException chaining connects a higher-level error to the lower-level exception that caused it. The syntax is `raise NewError(...) from error`.\n\nUse chaining when translating implementation details into a domain-specific error while preserving the original cause for debugging.\n\nThis is different from hiding the original exception. The caller can catch the domain error, and tooling can still inspect `__cause__`.\n\n:::program\n```python\nclass ConfigError(Exception):\n pass\n\n\ndef read_port(text):\n try:\n return int(text)\n except ValueError as error:\n raise ConfigError("port must be a number") from error\n\nprint(ConfigError.__name__)\n\ntry:\n read_port("abc")\nexcept ConfigError as error:\n print(error)\n print(type(error.__cause__).__name__)\n```\n:::\n\n:::cell\nCatch the low-level exception where it happens, then raise a domain-specific exception from it.\n\n```python\nclass ConfigError(Exception):\n pass\n\n\ndef read_port(text):\n try:\n return int(text)\n except ValueError as error:\n raise ConfigError("port must be a number") from error\n\nprint(ConfigError.__name__)\n```\n\n```output\nConfigError\n```\n:::\n\n:::cell\nThe caller handles the domain error. The original `ValueError` remains available as `__cause__`.\n\n```python\ntry:\n read_port("abc")\nexcept ConfigError as error:\n print(error)\n print(type(error.__cause__).__name__)\n```\n\n```output\nport must be a number\nValueError\n```\n:::\n\n:::note\n- Use `raise ... from error` when translating exceptions across a boundary.\n- The new exception\'s `__cause__` points to the original exception.\n- Chaining keeps user-facing errors clear without losing debugging context.\n:::\n', 'exception-groups.md': '+++\nslug = "exception-groups"\ntitle = "Exception Groups"\nsection = "Errors"\nsummary = "except* handles matching exceptions inside an ExceptionGroup."\ndoc_path = "/tutorial/errors.html#raising-and-handling-multiple-unrelated-exceptions"\nsee_also = [\n "exceptions",\n "exception-chaining",\n "async-await",\n]\n+++\n\n`ExceptionGroup` represents several unrelated exceptions raised together. `except*` exists for code that may receive multiple failures at once, especially concurrent work.\n\nUse ordinary `except` for one exception. Use `except*` only when the value being handled is an exception group and each matching subgroup needs its own handling.\n\nEach `except*` clause receives a smaller exception group containing the matching exceptions.\n\n:::program\n```python\nerrors = ExceptionGroup(\n "batch failed",\n [ValueError("bad port"), TypeError("bad mode")],\n)\nprint(len(errors.exceptions))\n\ntry:\n raise errors\nexcept* ValueError as group:\n print(type(group).__name__)\n print(group.exceptions[0])\nexcept* TypeError as group:\n print(group.exceptions[0])\n```\n:::\n\n:::cell\nAn exception group bundles several exception objects. This is different from an ordinary exception because more than one failure is present.\n\n```python\nerrors = ExceptionGroup(\n "batch failed",\n [ValueError("bad port"), TypeError("bad mode")],\n)\nprint(len(errors.exceptions))\n```\n\n```output\n2\n```\n:::\n\n:::cell\n`except*` handles matching members of the group. The `ValueError` handler sees the value error, and the `TypeError` handler sees the type error.\n\n```python\ntry:\n raise errors\nexcept* ValueError as group:\n print(type(group).__name__)\n print(group.exceptions[0])\nexcept* TypeError as group:\n print(group.exceptions[0])\n```\n\n```output\nExceptionGroup\nbad port\nbad mode\n```\n:::\n\n:::note\n- `except*` is for `ExceptionGroup`, not ordinary single exceptions.\n- Each `except*` clause handles matching members of the group.\n- Exception groups often appear around concurrent work.\n:::\n', 'exceptions.md': '+++\nslug = "exceptions"\ntitle = "Exceptions"\nsection = "Errors"\nsummary = "Use try, except, else, and finally to separate success, recovery, and cleanup."\ndoc_path = "/tutorial/errors.html"\n+++\n\nExceptions represent errors or unusual conditions that interrupt normal control flow. `try` marks the operation that may fail, and `except` handles a specific failure where recovery makes sense.\n\nKeep the successful path separate from the recovery path. `else` runs only when no exception was raised, while `finally` runs either way for cleanup or bookkeeping.\n\nUse exceptions when an operation cannot produce a valid result. Prefer ordinary conditionals for expected branches that are not errors.\n\nCatch specific exceptions whenever possible. A broad catch can hide programming mistakes, while a targeted `ValueError` handler documents exactly what failure is expected.\n\n:::program\n```python\ndef parse_int(text):\n return int(text)\n\nfor text in ["42", "python"]:\n try:\n number = parse_int(text)\n except ValueError:\n print(f"{text}: invalid")\n else:\n print(f"{text}: {number}")\n finally:\n print(f"checked {text}")\n```\n:::\n\n:::cell\nWhen no exception is raised, the `else` block runs. Keeping success in `else` makes the `try` block contain only the operation that might fail.\n\n```python\ndef parse_int(text):\n return int(text)\n\ntext = "42"\ntry:\n number = parse_int(text)\nexcept ValueError:\n print(f"{text}: invalid")\nelse:\n print(f"{text}: {number}")\nfinally:\n print(f"checked {text}")\n```\n\n```output\n42: 42\nchecked 42\n```\n:::\n\n:::cell\nWhen parsing fails, `int()` raises `ValueError`. Catching that specific exception makes the expected recovery path explicit.\n\n```python\ntext = "python"\ntry:\n number = parse_int(text)\nexcept ValueError:\n print(f"{text}: invalid")\nelse:\n print(f"{text}: {number}")\nfinally:\n print(f"checked {text}")\n```\n\n```output\npython: invalid\nchecked python\n```\n:::\n\n:::note\n- Catch the most specific exception you can.\n- `else` is for success code that should run only if the `try` block did not fail.\n- `finally` runs whether the operation succeeded or failed.\n:::\n', 'for-loops.md': '+++\nslug = "for-loops"\ntitle = "For Loops"\nsection = "Control Flow"\nsummary = "for iterates over any iterable object."\ndoc_path = "/tutorial/controlflow.html#for-statements"\n+++\n\nPython for loops iterate over values from an iterable. This is different from languages where for primarily means incrementing a numeric counter.\n\nrange() is itself an iterable that produces numbers lazily. Use it when you need a sequence of integers, but prefer direct iteration when you already have a collection.\n\nBlocks are defined by indentation. range(3) yields 0, 1, and 2.\n\n:::program\n```python\nfor name in ["Ada", "Grace", "Guido"]:\n print(name)\n\nfor number in range(3):\n print(number)\n```\n:::\n\n:::cell\nPython for loops iterate over values from an iterable. This is different from languages where for primarily means incrementing a numeric counter.\n\n```python\nfor name in ["Ada", "Grace", "Guido"]:\n print(name)\n```\n\n```output\nAda\nGrace\nGuido\n```\n:::\n\n:::cell\nrange() is itself an iterable that produces numbers lazily. Use it when you need a sequence of integers, but prefer direct iteration when you already have a collection.\n\n```python\nfor number in range(3):\n print(number)\n```\n\n```output\n0\n1\n2\n```\n:::\n\n:::note\n- Blocks are defined by indentation.\n- range(3) yields 0, 1, and 2.\n:::\n', 'functions.md': '+++\nslug = "functions"\ntitle = "Functions"\nsection = "Functions"\nsummary = "Use def to name reusable behavior and return results."\ndoc_path = "/tutorial/controlflow.html#defining-functions"\n+++\n\nFunctions package behavior behind a name. `def` creates a function object that can accept arguments, compute values, and return a result.\n\nDefault arguments make common calls short, and keyword arguments make call sites easier to read. A function that reaches the end without `return` produces `None`.\n\nUse functions when a calculation has a useful name, when code repeats, or when a piece of behavior should be tested independently.\n\n:::program\n```python\ndef greet(name):\n return f"Hello, {name}."\n\nprint(greet("Python"))\n\n\ndef format_total(amount, currency="USD"):\n return f"{amount} {currency}"\n\nprint(format_total(10))\nprint(format_total(10, currency="EUR"))\n\n\ndef log(message):\n print(f"log: {message}")\n\nresult = log("saved")\nprint(result)\n```\n:::\n\n:::cell\n`return` sends a value back to the caller. The caller can print it, store it, or pass it to another function.\n\n```python\ndef greet(name):\n return f"Hello, {name}."\n\nprint(greet("Python"))\n```\n\n```output\nHello, Python.\n```\n:::\n\n:::cell\nDefault arguments provide common values. Keyword arguments make it clear which option is being overridden.\n\n```python\ndef format_total(amount, currency="USD"):\n return f"{amount} {currency}"\n\nprint(format_total(10))\nprint(format_total(10, currency="EUR"))\n```\n\n```output\n10 USD\n10 EUR\n```\n:::\n\n:::cell\nA function without an explicit `return` returns `None`. That makes side-effect-only functions easy to distinguish from value-producing ones.\n\n```python\ndef log(message):\n print(f"log: {message}")\n\nresult = log("saved")\nprint(result)\n```\n\n```output\nlog: saved\nNone\n```\n:::\n\n:::note\n- Use `return` for values the caller should receive.\n- Defaults keep common calls concise.\n- Keyword arguments make options readable at the call site.\n:::\n', 'generator-expressions.md': '+++\nslug = "generator-expressions"\ntitle = "Generator Expressions"\nsection = "Iteration"\nsummary = "Generator expressions use comprehension-like syntax to stream values lazily."\ndoc_path = "/tutorial/classes.html#generator-expressions"\n+++\n\nGenerator expressions look like list comprehensions with parentheses, but they produce an iterator instead of building a concrete collection immediately.\n\nUse them when a consumer such as `sum()`, `any()`, or a `for` loop can use values one at a time. This keeps the transformation close to the consumer and avoids storing intermediate lists.\n\nLike other iterators, a generator expression is consumed as values are requested. Create a new generator expression when you need another pass.\n\n:::program\n```python\nnumbers = [1, 2, 3, 4]\nlist_squares = [number * number for number in numbers]\nprint(list_squares)\n\nstream_squares = (number * number for number in numbers)\nprint(next(stream_squares))\nprint(next(stream_squares))\nprint(list(stream_squares))\n\nprint(sum(number * number for number in numbers))\n```\n:::\n\n:::cell\nA list comprehension is eager: it builds a list immediately. That is useful when you need to store or reuse the results.\n\n```python\nnumbers = [1, 2, 3, 4]\nlist_squares = [number * number for number in numbers]\nprint(list_squares)\n```\n\n```output\n[1, 4, 9, 16]\n```\n:::\n\n:::cell\nA generator expression is lazy: it creates an iterator that produces values as they are consumed. After two `next()` calls, only the remaining squares are left.\n\n```python\nstream_squares = (number * number for number in numbers)\nprint(next(stream_squares))\nprint(next(stream_squares))\nprint(list(stream_squares))\n```\n\n```output\n1\n4\n[9, 16]\n```\n:::\n\n:::cell\nGenerator expressions are common inside reducing functions. When a generator expression is the only argument, the extra parentheses can be omitted.\n\n```python\nprint(sum(number * number for number in numbers))\n```\n\n```output\n30\n```\n:::\n\n:::note\n- List, dict, and set comprehensions build concrete collections.\n- Generator expressions produce one-pass iterators.\n- Use generator expressions when the consumer can process values one at a time.\n:::\n', 'generators.md': '+++\nslug = "generators"\ntitle = "Generators"\nsection = "Iteration"\nsummary = "yield creates an iterator that produces values on demand."\ndoc_path = "/tutorial/classes.html#generators"\n+++\n\nA generator function is a convenient way to write your own iterator. `yield` produces one value, pauses the function, and resumes when the next value is requested.\n\nGenerators are useful for pipelines, large inputs, and infinite sequences because they avoid building an entire collection in memory.\n\nUse `next()` to request one value manually, or loop over the generator to consume values until it is exhausted.\n\n:::program\n```python\ndef countdown(n):\n while n > 0:\n yield n\n n -= 1\n\nnumbers = countdown(3)\nprint(next(numbers))\nprint(next(numbers))\n\nfor value in countdown(3):\n print(value)\n```\n:::\n\n:::cell\nCalling a generator function returns an iterator. `next()` asks for one value and resumes the function until the next `yield`.\n\n```python\ndef countdown(n):\n while n > 0:\n yield n\n n -= 1\n\nnumbers = countdown(3)\nprint(next(numbers))\nprint(next(numbers))\n```\n\n```output\n3\n2\n```\n:::\n\n:::cell\nA `for` loop repeatedly calls `next()` for you. The loop stops when the generator is exhausted.\n\n```python\nfor value in countdown(3):\n print(value)\n```\n\n```output\n3\n2\n1\n```\n:::\n\n:::note\n- Generator functions are a concise way to create custom iterators.\n- Values are produced on demand.\n- A generator is consumed as you iterate over it.\n- Prefer a list when you need to reuse stored results; prefer a generator when values can be streamed once.\n:::\n', 'generics-and-typevar.md': '+++\nslug = "generics-and-typevar"\ntitle = "Generics and TypeVar"\nsection = "Types"\nsummary = "Generics preserve type information across reusable functions and classes."\ndoc_path = "/library/typing.html#generics"\nsee_also = [\n "type-hints",\n "collections-module",\n "casts-and-any",\n]\n+++\n\nGenerics connect types across an API. A plain function that returns `object` loses information; a generic function can say that the returned value has the same type as the input element.\n\nA `TypeVar` stands for a type chosen by the caller. In `list[T] -> T`, the same `T` says that a list of strings produces a string and a list of integers produces an integer.\n\nUse generics when a function or class is reusable but still preserves a relationship between input and output types.\n\n:::program\n```python\nfrom typing import TypeVar\n\nT = TypeVar("T")\n\n\ndef first(items: list[T]) -> T:\n return items[0]\n\n\ndef pair(left: T, right: T) -> tuple[T, T]:\n return (left, right)\n\nprint(first([1, 2, 3]))\nprint(first(["Ada", "Grace"]))\nprint(pair("x", "y"))\nprint(T.__name__)\n```\n:::\n\n:::cell\nA `TypeVar` stands for a type chosen by the caller. The return type follows the list element type.\n\n```python\nfrom typing import TypeVar\n\nT = TypeVar("T")\n\n\ndef first(items: list[T]) -> T:\n return items[0]\n\nprint(first([1, 2, 3]))\nprint(first(["Ada", "Grace"]))\n```\n\n```output\n1\nAda\n```\n:::\n\n:::cell\nReusing the same `TypeVar` expresses a relationship between parameters and results.\n\n```python\ndef pair(left: T, right: T) -> tuple[T, T]:\n return (left, right)\n\nprint(pair("x", "y"))\n```\n\n```output\n(\'x\', \'y\')\n```\n:::\n\n:::cell\n`TypeVar` is visible at runtime, but the relationship is mainly for type checkers.\n\n```python\nprint(T.__name__)\nprint(first.__annotations__)\n```\n\n```output\nT\n{\'items\': list[~T], \'return\': ~T}\n```\n:::\n\n:::note\n- A `TypeVar` stands for a type chosen by the caller.\n- Generic functions avoid losing information to `object` or `Any`.\n- Use generics when input and output types are connected.\n:::\n', 'guard-clauses.md': '+++\nslug = "guard-clauses"\ntitle = "Guard Clauses"\nsection = "Control Flow"\nsummary = "Guard clauses handle exceptional cases early so the main path stays flat."\ndoc_path = "/tutorial/controlflow.html#if-statements"\n+++\n\nGuard clauses handle exceptional cases early so the main path stays flat. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.\n\nUse it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.\n\nThe example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.\n\n:::program\n```python\ndef price_after_discount(price, percent):\n if price < 0:\n return "invalid price"\n if not 0 <= percent <= 100:\n return "invalid discount"\n\n discount = price * percent / 100\n return round(price - discount, 2)\n\nprint(price_after_discount(100, 15))\nprint(price_after_discount(-5, 10))\nprint(price_after_discount(100, 120))\n```\n:::\n\n:::cell\nReturn early when inputs cannot be handled.\n\n```python\ndef price_after_discount(price, percent):\n if price < 0:\n return "invalid price"\n if not 0 <= percent <= 100:\n return "invalid discount"\n\n discount = price * percent / 100\n return round(price - discount, 2)\n\nprint(price_after_discount(100, 15))\nprint(price_after_discount(-5, 10))\nprint(price_after_discount(100, 120))\n```\n\n```output\n85.0\ninvalid price\ninvalid discount\n```\n:::\n\n:::note\n- Return early when inputs cannot be handled.\n- After the guards, the remaining code can read as the normal path.\n- Guard clauses are a style choice, not new syntax.\n:::\n', 'hello-world.md': '+++\nslug = "hello-world"\ntitle = "Hello World"\nsection = "Basics"\nsummary = "The first Python program prints a line of text."\ndoc_path = "/tutorial/introduction.html"\n+++\n\nEvery Python program starts by executing statements from top to bottom. Calling `print()` is the smallest useful program because it shows how Python evaluates an expression and sends text to standard output.\n\nStrings are ordinary values, so the message passed to `print()` can be changed, stored in a variable, or produced by a function. This example keeps the first program intentionally small.\n\n`print()` writes text followed by a newline. Strings can be delimited with single or double quotes.\n\n:::program\n```python\nprint("hello world")\n```\n:::\n\n:::cell\nEvery Python program starts by executing statements from top to bottom. Calling `print()` is the smallest useful program because it shows how Python evaluates an expression and sends text to standard output.\n\n```python\nprint("hello world")\n```\n\n```output\nhello world\n```\n:::\n\n:::note\n- `print()` writes text followed by a newline.\n- Strings can be delimited with single or double quotes.\n:::\n', 'import-aliases.md': '+++\nslug = "import-aliases"\ntitle = "Import Aliases"\nsection = "Modules"\nsummary = "as gives imported modules or names a local alias."\ndoc_path = "/reference/simple_stmts.html#the-import-statement"\nsee_also = [\n "modules",\n "functions",\n]\n+++\n\n`as` gives an imported module or imported name a local alias. Use it when a conventional short name improves readability or when two imports would otherwise collide.\n\nThe alternative is a plain import, which is usually better when the module name is already clear. Avoid aliases that make readers guess where a name came from.\n\nAvoid star imports in examples and production modules because they hide dependencies and blur the boundary between modules.\n\n:::program\n```python\nimport statistics as stats\nfrom math import sqrt as square_root\n\nscores = [8, 10, 9]\nprint(stats.mean(scores))\nprint(stats.__name__)\n\nprint(square_root(81))\nprint(square_root.__name__)\n```\n:::\n\n:::cell\nA module alias keeps the namespace but changes the local name. Here `stats` is shorter, but readers can still see that `mean` belongs to the statistics module.\n\n```python\nimport statistics as stats\n\nscores = [8, 10, 9]\nprint(stats.mean(scores))\nprint(stats.__name__)\n```\n\n```output\n9\nstatistics\n```\n:::\n\n:::cell\nA name imported with `from` can also be aliased. Use this when the local name explains the role better than the original name.\n\n```python\nfrom math import sqrt as square_root\n\nprint(square_root(81))\nprint(square_root.__name__)\n```\n\n```output\n9.0\nsqrt\n```\n:::\n\n:::note\n- `import module as alias` keeps module-style access under a shorter or clearer name.\n- `from module import name as alias` imports one name under a local alias.\n- Prefer plain imports unless an alias improves clarity or follows a strong convention.\n- Avoid `from module import *` because it makes dependencies harder to see.\n:::\n', 'inheritance-and-super.md': '+++\nslug = "inheritance-and-super"\ntitle = "Inheritance and Super"\nsection = "Classes"\nsummary = "Inheritance reuses behavior, and super delegates to a parent implementation."\ndoc_path = "/tutorial/classes.html#inheritance"\nsee_also = [\n "classes",\n "properties",\n "special-methods",\n]\n+++\n\nInheritance lets one class specialize another class. The child class gets parent behavior and can add or override methods.\n\nUse `super()` when the child method should extend the parent implementation instead of replacing it entirely.\n\nPrefer composition when objects merely collaborate. Inheritance is best when the child really is a specialized version of the parent.\n\n:::program\n```python\nclass Animal:\n def __init__(self, name):\n self.name = name\n\n def speak(self):\n return f"{self.name} makes a sound"\n\nclass Dog(Animal):\n def speak(self):\n base = super().speak()\n return f"{base}; {self.name} barks"\n\npet = Dog("Nina")\nprint(pet.name)\nprint(pet.speak())\nprint(isinstance(pet, Animal))\n```\n:::\n\n:::cell\nA child class names its parent in parentheses. `Dog` instances get the `Animal.__init__` method because `Dog` does not define its own initializer.\n\n```python\nclass Animal:\n def __init__(self, name):\n self.name = name\n\n def speak(self):\n return f"{self.name} makes a sound"\n\nclass Dog(Animal):\n def speak(self):\n base = super().speak()\n return f"{base}; {self.name} barks"\n\npet = Dog("Nina")\nprint(pet.name)\n```\n\n```output\nNina\n```\n:::\n\n:::cell\n`super()` delegates to the parent implementation. The child method can reuse the parent result and then add specialized behavior.\n\n```python\nprint(pet.speak())\nprint(isinstance(pet, Animal))\n```\n\n```output\nNina makes a sound; Nina barks\nTrue\n```\n:::\n\n:::note\n- Inheritance models an “is a specialized kind of” relationship.\n- `super()` calls the next implementation in the method resolution order.\n- Prefer composition when an object only needs to use another object.\n:::\n', 'iterating-over-iterables.md': '+++\nslug = "iterating-over-iterables"\ntitle = "Iterating over Iterables"\nsection = "Iteration"\nsummary = "for loops consume values from any iterable object."\ndoc_path = "/tutorial/controlflow.html#for-statements"\n+++\n\nPython\'s `for` statement consumes values from any iterable object: lists, strings, dictionaries, ranges, generators, files, and many standard-library helpers.\n\nThis makes iteration a value-stream protocol rather than a special case for arrays. The producer decides how values are made, and the loop consumes them one at a time.\n\nUse `enumerate()` when you need positions and values together, and `dict.items()` when you need keys and values. These helpers express intent better than manual indexing.\n\n:::program\n```python\nnames = ["Ada", "Grace", "Guido"]\n\nfor name in names:\n print(name)\n\nfor index, name in enumerate(names):\n print(index, name)\n\nscores = {"Ada": 10, "Grace": 9}\nfor name, score in scores.items():\n print(name, score)\n```\n:::\n\n:::cell\nStart with an ordinary list. A list stores values, and a `for` loop asks it for one value at a time.\n\nWhen you only need the values, iterate over the collection directly. There is no index variable because the loop body does not need one.\n\n```python\nnames = ["Ada", "Grace", "Guido"]\n\nfor name in names:\n print(name)\n```\n\n```output\nAda\nGrace\nGuido\n```\n:::\n\n:::cell\nWhen you need both a position and a value, use `enumerate()`. It produces index/value pairs without manual indexing.\n\n```python\nfor index, name in enumerate(names):\n print(index, name)\n```\n\n```output\n0 Ada\n1 Grace\n2 Guido\n```\n:::\n\n:::cell\nDictionaries are iterable too, but `dict.items()` is the clearest way to say that the loop needs keys and values together.\n\n```python\nscores = {"Ada": 10, "Grace": 9}\nfor name, score in scores.items():\n print(name, score)\n```\n\n```output\nAda 10\nGrace 9\n```\n:::\n\n:::note\n- A `for` loop consumes values from an iterable.\n- Different producers can feed the same loop protocol.\n- Prefer `enumerate()` over `range(len(...))` when you need an index.\n:::\n', 'iterators.md': '+++\nslug = "iterators"\ntitle = "Iterators"\nsection = "Iteration"\nsummary = "iter and next expose the protocol behind for loops."\ndoc_path = "/library/stdtypes.html#iterator-types"\n+++\n\nAn iterable is an object that can produce values for a loop. An iterator is the object that remembers where that production currently is.\n\n`iter()` asks an iterable for an iterator, and `next()` consumes one value from that iterator. A `for` loop performs those steps for you until the iterator is exhausted.\n\nThis is the core value-stream protocol in Python: one object produces values, another piece of code consumes them, and many streams are one-pass.\n\n:::program\n```python\nnames = ["Ada", "Grace", "Guido"]\niterator = iter(names)\nprint(next(iterator))\nprint(next(iterator))\n\nfor name in iterator:\n print(name)\n\nagain = iter(names)\nprint(next(again))\n```\n:::\n\n:::cell\n`iter()` asks an iterable for an iterator. `next()` consumes one value and advances the iterator\'s position.\n\n```python\nnames = ["Ada", "Grace", "Guido"]\niterator = iter(names)\nprint(next(iterator))\nprint(next(iterator))\n```\n\n```output\nAda\nGrace\n```\n:::\n\n:::cell\nA `for` loop consumes the same iterator protocol. Because two values were already consumed, the loop sees only the remaining value.\n\n```python\nfor name in iterator:\n print(name)\n```\n\n```output\nGuido\n```\n:::\n\n:::cell\nThe list itself is reusable. Asking it for a fresh iterator starts a new pass over the same stored values.\n\n```python\nagain = iter(names)\nprint(next(again))\n```\n\n```output\nAda\n```\n:::\n\n:::note\n- Iterables produce iterators; iterators produce values.\n- `next()` consumes one value from an iterator.\n- Many iterators are one-pass even when the original collection is reusable.\n:::\n', 'itertools.md': '+++\nslug = "itertools"\ntitle = "Itertools"\nsection = "Iteration"\nsummary = "itertools composes lazy iterator streams."\ndoc_path = "/library/itertools.html"\n+++\n\nThe `itertools` module contains tools for composing iterator streams: combining, slicing, grouping, and repeating values without changing the consumer protocol.\n\nMany `itertools` functions are lazy. They describe work to do later instead of building a list immediately, so helpers such as `islice()` are useful when taking a finite window.\n\nIterator pipelines let each step stay small: one object produces values, another transforms them, and a final consumer such as `list()` or a loop pulls values through the pipeline.\n\n:::program\n```python\nimport itertools\n\ncounter = itertools.count(10)\nprint(list(itertools.islice(counter, 3)))\n\npages = itertools.chain(["intro", "setup"], ["deploy"])\nprint(list(pages))\n\nscores = [7, 10, 8, 10]\nhigh_scores = itertools.compress(scores, [score >= 9 for score in scores])\nprint(list(high_scores))\n```\n:::\n\n:::cell\n`count()` can produce values forever, so `islice()` takes a finite window. Nothing is materialized until `list()` consumes the iterator.\n\n```python\nimport itertools\n\ncounter = itertools.count(10)\nprint(list(itertools.islice(counter, 3)))\n```\n\n```output\n[10, 11, 12]\n```\n:::\n\n:::cell\n`chain()` presents several iterables as one stream. This avoids building an intermediate list just to loop over combined inputs.\n\n```python\npages = itertools.chain(["intro", "setup"], ["deploy"])\nprint(list(pages))\n```\n\n```output\n[\'intro\', \'setup\', \'deploy\']\n```\n:::\n\n:::cell\nIterator helpers compose with ordinary Python expressions. `compress()` keeps items whose corresponding selector is true.\n\n```python\nscores = [7, 10, 8, 10]\nhigh_scores = itertools.compress(scores, [score >= 9 for score in scores])\nprint(list(high_scores))\n```\n\n```output\n[10, 10]\n```\n:::\n\n:::note\n- `itertools` composes producer and transformer streams.\n- Iterator pipelines avoid building intermediate lists.\n- Use `islice()` to take a finite piece from an infinite iterator.\n- Convert to a list only when you need concrete results.\n:::\n', 'json.md': '+++\nslug = "json"\ntitle = "JSON"\nsection = "Standard Library"\nsummary = "json encodes Python values as JSON text and decodes them back."\ndoc_path = "/library/json.html"\nsee_also = [\n "dicts",\n "typed-dicts",\n "strings",\n]\n+++\n\nThe `json` module converts between Python values and JSON text. Dictionaries, lists, strings, numbers, booleans, and `None` map naturally to JSON structures.\n\nUse `dumps()` when you need a string and `loads()` when you need Python objects back. Options such as `sort_keys=True` and `indent=2` control stable, readable output.\n\nJSON is a data format, not a way to preserve arbitrary Python objects. Encode simple data structures at service boundaries, and expect decode errors when the incoming text is not valid JSON.\n\n:::program\n```python\nimport json\n\npayload = {"language": "Python", "versions": [3, 13], "stable": True, "missing": None}\ntext = json.dumps(payload, sort_keys=True)\nprint(text)\n\npretty = json.dumps({"language": "Python", "stable": True}, indent=2, sort_keys=True)\nprint(pretty.splitlines()[0])\nprint(pretty.splitlines()[1])\n\ndecoded = json.loads(text)\nprint(decoded["language"])\nprint(decoded["missing"] is None)\n\ntry:\n json.loads("{bad json}")\nexcept json.JSONDecodeError as error:\n print(error.__class__.__name__)\n```\n:::\n\n:::cell\n`dumps()` encodes Python data as JSON text. `sort_keys=True` keeps dictionary keys in a stable order for reproducible output.\n\n```python\nimport json\n\npayload = {"language": "Python", "versions": [3, 13], "stable": True, "missing": None}\ntext = json.dumps(payload, sort_keys=True)\nprint(text)\n```\n\n```output\n{"language": "Python", "missing": null, "stable": true, "versions": [3, 13]}\n```\n:::\n\n:::cell\nFormatting options change the JSON text, not the Python value. `indent=2` is useful for human-readable output.\n\n```python\npretty = json.dumps({"language": "Python", "stable": True}, indent=2, sort_keys=True)\nprint(pretty.splitlines()[0])\nprint(pretty.splitlines()[1])\n```\n\n```output\n{\n "language": "Python",\n```\n:::\n\n:::cell\n`loads()` decodes JSON text back into Python values. JSON `null` becomes Python `None`.\n\n```python\ndecoded = json.loads(text)\nprint(decoded["language"])\nprint(decoded["missing"] is None)\n```\n\n```output\nPython\nTrue\n```\n:::\n\n:::cell\nInvalid JSON raises `JSONDecodeError`, so input boundaries should handle decode failures explicitly.\n\n```python\ntry:\n json.loads("{bad json}")\nexcept json.JSONDecodeError as error:\n print(error.__class__.__name__)\n```\n\n```output\nJSONDecodeError\n```\n:::\n\n:::note\n- `dumps()` returns a string; `loads()` accepts a string.\n- JSON `true`, `false`, and `null` become Python `True`, `False`, and `None`.\n- Use `sort_keys=True` when stable text output matters.\n- JSON only represents data shapes, not arbitrary Python objects or behavior.\n:::\n', 'keyword-only-arguments.md': '+++\nslug = "keyword-only-arguments"\ntitle = "Keyword-only Arguments"\nsection = "Functions"\nsummary = "Use * to require selected function arguments to be named."\ndoc_path = "/tutorial/controlflow.html#special-parameters"\n+++\n\nA bare `*` in a function signature marks the following parameters as keyword-only. Callers must name those arguments explicitly.\n\nKeyword-only arguments are useful for options such as timeouts, flags, and modes where positional calls would be ambiguous or easy to misread.\n\nThey let the required data stay positional while optional controls remain self-documenting at the call site.\n\n:::program\n```python\ndef connect(host, *, timeout=5, secure=True):\n scheme = "https" if secure else "http"\n print(f"{scheme}://{host} timeout={timeout}")\n\nconnect("example.com")\nconnect("example.com", timeout=10)\nconnect("localhost", secure=False)\n```\n:::\n\n:::cell\nParameters after `*` must be named. The default options still apply when the caller omits them.\n\n```python\ndef connect(host, *, timeout=5, secure=True):\n scheme = "https" if secure else "http"\n print(f"{scheme}://{host} timeout={timeout}")\n\nconnect("example.com")\n```\n\n```output\nhttps://example.com timeout=5\n```\n:::\n\n:::cell\nNaming the option makes the call site explicit. A reader does not have to remember which positional slot controls the timeout.\n\n```python\nconnect("example.com", timeout=10)\n```\n\n```output\nhttps://example.com timeout=10\n```\n:::\n\n:::cell\nFlags are especially good keyword-only arguments because a bare positional `False` is hard to interpret.\n\n```python\nconnect("localhost", secure=False)\n```\n\n```output\nhttp://localhost timeout=5\n```\n:::\n\n:::note\n- Put `*` before options that callers should name.\n- Keyword-only flags avoid mysterious positional `True` and `False` arguments.\n- Defaults work normally for keyword-only parameters.\n:::\n', 'lambdas.md': '+++\nslug = "lambdas"\ntitle = "Lambdas"\nsection = "Functions"\nsummary = "lambda creates small anonymous function expressions."\ndoc_path = "/tutorial/controlflow.html#lambda-expressions"\n+++\n\n`lambda` creates a small anonymous function expression. It is most useful when Python asks for a function and the behavior is short enough to read inline.\n\nA lambda can only contain one expression. Use `def` when the behavior deserves a name, needs statements, or would be easier to test separately.\n\nLambdas often appear as key functions, callbacks, and tiny adapters. Keep them simple enough that the call site remains clearer than a named helper.\n\n:::program\n```python\nadd_tax = lambda price: round(price * 1.08, 2)\nprint(add_tax(10))\n\nitems = [("notebook", 5), ("pen", 2), ("bag", 20)]\nby_price = sorted(items, key=lambda item: item[1])\nprint(by_price)\n\ndef price(item):\n return item[1]\n\nprint(sorted(items, key=price))\n```\n:::\n\n:::cell\nA lambda is a function expression. Assigning one to a name works, although `def` is usually clearer for reusable behavior.\n\n```python\nadd_tax = lambda price: round(price * 1.08, 2)\nprint(add_tax(10))\n```\n\n```output\n10.8\n```\n:::\n\n:::cell\nLambdas are most idiomatic when passed directly to another function. `sorted()` calls this key function once for each item.\n\n```python\nitems = [("notebook", 5), ("pen", 2), ("bag", 20)]\nby_price = sorted(items, key=lambda item: item[1])\nprint(by_price)\n```\n\n```output\n[(\'pen\', 2), (\'notebook\', 5), (\'bag\', 20)]\n```\n:::\n\n:::cell\nA named function is better when the behavior should be reused or explained. It produces the same sort key, but gives the operation a name.\n\n```python\ndef price(item):\n return item[1]\n\nprint(sorted(items, key=price))\n```\n\n```output\n[(\'pen\', 2), (\'notebook\', 5), (\'bag\', 20)]\n```\n:::\n\n:::note\n- Lambdas are expressions, not statements.\n- Prefer `def` for multi-step or reused behavior.\n- Lambdas are common as `key=` functions because the behavior is local to one call.\n:::\n', 'lists.md': '+++\nslug = "lists"\ntitle = "Lists"\nsection = "Collections"\nsummary = "Lists are ordered, mutable collections."\ndoc_path = "/tutorial/datastructures.html#more-on-lists"\n+++\n\nLists are Python\'s general-purpose mutable sequence type. Use them when order matters and the collection may grow, shrink, or be rearranged.\n\nIndexing reads individual positions. `0` is the first item, and negative indexes count backward from the end.\n\nMutation and copying matter: `append()` changes the list, while `sorted()` returns a new ordered list and leaves the original alone.\n\n:::program\n```python\nnumbers = [3, 1, 4]\nnumbers.append(1)\n\nprint(numbers)\nprint(numbers[0])\nprint(numbers[-1])\nprint(sorted(numbers))\nprint(numbers)\n```\n:::\n\n:::cell\nCreate a list with square brackets. Because lists are mutable, `append()` changes this same list object.\n\n```python\nnumbers = [3, 1, 4]\nnumbers.append(1)\n\nprint(numbers)\n```\n\n```output\n[3, 1, 4, 1]\n```\n:::\n\n:::cell\nUse indexes to read positions. Negative indexes are convenient for reading from the end.\n\n```python\nprint(numbers[0])\nprint(numbers[-1])\n```\n\n```output\n3\n1\n```\n:::\n\n:::cell\nUse `sorted()` when you want an ordered copy and still need the original order afterward.\n\n```python\nprint(sorted(numbers))\nprint(numbers)\n```\n\n```output\n[1, 1, 3, 4]\n[3, 1, 4, 1]\n```\n:::\n\n:::note\n- Lists are mutable sequences: methods such as `append()` change the list in place.\n- Negative indexes count from the end.\n- `sorted()` returns a new list; `list.sort()` sorts the existing list in place.\n:::\n', 'literal-and-final.md': '+++\nslug = "literal-and-final"\ntitle = "Literal and Final"\nsection = "Types"\nsummary = "Literal restricts values, while Final marks names that should not be rebound."\ndoc_path = "/library/typing.html#typing.Literal"\n+++\n\nLiteral restricts values, while Final marks names that should not be rebound. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.\n\nUse it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.\n\nThe example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.\n\n:::program\n```python\nfrom typing import Final, Literal\n\nMode = Literal["read", "write"]\nDEFAULT_MODE: Final[Mode] = "read"\n\n\ndef open_label(mode: Mode) -> str:\n return f"opening for {mode}"\n\nprint(open_label(DEFAULT_MODE))\nprint(open_label("write"))\nprint(DEFAULT_MODE)\n```\n:::\n\n:::cell\n`Literal` describes a small set of exact allowed values.\n\n```python\nfrom typing import Final, Literal\n\nMode = Literal["read", "write"]\nDEFAULT_MODE: Final[Mode] = "read"\n\n\ndef open_label(mode: Mode) -> str:\n return f"opening for {mode}"\n\nprint(open_label(DEFAULT_MODE))\nprint(open_label("write"))\nprint(DEFAULT_MODE)\n```\n\n```output\nopening for read\nopening for write\nread\n```\n:::\n\n:::note\n- `Literal` describes a small set of exact allowed values.\n- `Final` tells type checkers that a name should not be rebound.\n- Both are static promises; ordinary runtime assignment rules still apply.\n:::\n', 'literals.md': '+++\nslug = "literals"\ntitle = "Literals"\nsection = "Basics"\nsummary = "Literals write values directly in Python source code."\ndoc_path = "/reference/lexical_analysis.html#literals"\nsee_also = [\n "values",\n "strings",\n "bytes-and-bytearray",\n "dicts",\n]\n+++\n\nLiterals are source-code forms for values: numbers, text, bytes, containers, booleans, `None`, and a few specialized markers. They are how a program writes small values directly.\n\nThe literal form is only the beginning. Later examples explain each value family in depth: strings are Unicode text, bytes are binary data, lists and dicts are containers, and `None` represents intentional absence.\n\nUse literals when the value is small and local. Give repeated or meaningful values a name so the program explains why that value matters.\n\n:::program\n```python\nwhole = 42\nfraction = 3.5\ncomplex_number = 2 + 3j\nprint(whole, fraction, complex_number.imag)\n\ntext = "python"\nraw_pattern = r"\\d+"\ndata = b"py"\nprint(text)\nprint(raw_pattern)\nprint(data)\n\npoint = (2, 3)\nnames = ["Ada", "Grace"]\nscores = {"Ada": 98}\nunique = {"py", "go"}\nprint(point)\nprint(names[0])\nprint(scores["Ada"])\nprint(sorted(unique))\n\nprint(True, False, None)\nprint(...)\n```\n:::\n\n:::cell\nNumeric literals write numbers directly. Complex literals use `j` for the imaginary part.\n\n```python\nwhole = 42\nfraction = 3.5\ncomplex_number = 2 + 3j\nprint(whole, fraction, complex_number.imag)\n```\n\n```output\n42 3.5 3.0\n```\n:::\n\n:::cell\nString literals write Unicode text. Raw strings keep backslashes literal, and bytes literals write binary data rather than text.\n\n```python\ntext = "python"\nraw_pattern = r"\\d+"\ndata = b"py"\nprint(text)\nprint(raw_pattern)\nprint(data)\n```\n\n```output\npython\n\\d+\nb\'py\'\n```\n:::\n\n:::cell\nContainer literals create tuples, lists, dictionaries, and sets. Each container answers a different question about order, position, lookup, or uniqueness.\n\n```python\npoint = (2, 3)\nnames = ["Ada", "Grace"]\nscores = {"Ada": 98}\nunique = {"py", "go"}\nprint(point)\nprint(names[0])\nprint(scores["Ada"])\nprint(sorted(unique))\n```\n\n```output\n(2, 3)\nAda\n98\n[\'go\', \'py\']\n```\n:::\n\n:::cell\n`True`, `False`, `None`, and `...` are singleton literal-like constants used for truth values, absence, and placeholders.\n\n```python\nprint(True, False, None)\nprint(...)\n```\n\n```output\nTrue False None\nEllipsis\n```\n:::\n\n:::note\n- Literals are good for small local values; constants are better for repeated values with meaning.\n- `{}` is an empty dictionary. Use `set()` for an empty set.\n- Bytes literals are binary data; string literals are Unicode text.\n- `...` evaluates to the `Ellipsis` object.\n:::\n', 'logging.md': '+++\nslug = "logging"\ntitle = "Logging"\nsection = "Standard Library"\nsummary = "logging records operational events without using print as infrastructure."\ndoc_path = "/library/logging.html"\n+++\n\nlogging records operational events without using print as infrastructure. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.\n\nUse it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.\n\nThe example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.\n\n:::program\n```python\nimport logging\nimport sys\n\nlogger = logging.getLogger("example")\nlogger.setLevel(logging.INFO)\nhandler = logging.StreamHandler(sys.stdout)\nhandler.setFormatter(logging.Formatter("%(levelname)s:%(message)s"))\nlogger.handlers[:] = [handler]\n\nlogger.debug("hidden")\nlogger.info("service started")\nlogger.warning("disk almost full")\n```\n:::\n\n:::cell\nLoggers name where an event came from.\n\n```python\nimport logging\nimport sys\n\nlogger = logging.getLogger("example")\nlogger.setLevel(logging.INFO)\nhandler = logging.StreamHandler(sys.stdout)\nhandler.setFormatter(logging.Formatter("%(levelname)s:%(message)s"))\nlogger.handlers[:] = [handler]\n\nlogger.debug("hidden")\nlogger.info("service started")\nlogger.warning("disk almost full")\n```\n\n```output\nINFO:service started\nWARNING:disk almost full\n```\n:::\n\n:::note\n- Loggers name where an event came from.\n- Handlers decide where records go.\n- Levels let operators choose how much detail to see.\n:::\n', 'loop-else.md': '+++\nslug = "loop-else"\ntitle = "Loop Else"\nsection = "Control Flow"\nsummary = "A loop else block runs only when the loop did not end with break."\ndoc_path = "/tutorial/controlflow.html#else-clauses-on-loops"\nsee_also = [\n "break-and-continue",\n "for-loops",\n "while-loops",\n]\n+++\n\nPython loops can have an `else` clause. The name is surprising at first: loop `else` means “no `break` happened,” not “the loop condition was false.”\n\nThis is useful for searches. Put the successful early exit in `break`, then put the not-found path in `else`.\n\nUse loop `else` sparingly. It is clearest when the loop is visibly searching for something.\n\n:::program\n```python\nnames = ["Ada", "Grace", "Guido"]\n\nfor name in names:\n if name == "Grace":\n print("found")\n break\nelse:\n print("missing")\n\nfor name in names:\n if name == "Linus":\n print("found")\n break\nelse:\n print("missing")\n```\n:::\n\n:::cell\nIf the loop reaches `break`, the `else` block is skipped. This branch means the search succeeded early.\n\n```python\nnames = ["Ada", "Grace", "Guido"]\n\nfor name in names:\n if name == "Grace":\n print("found")\n break\nelse:\n print("missing")\n```\n\n```output\nfound\n```\n:::\n\n:::cell\nIf the loop finishes without `break`, the `else` block runs. This branch means the search examined every value and found nothing.\n\n```python\nfor name in names:\n if name == "Linus":\n print("found")\n break\nelse:\n print("missing")\n```\n\n```output\nmissing\n```\n:::\n\n:::note\n- Loop `else` runs when the loop was not ended by `break`.\n- It is best for search loops with a clear found/not-found split.\n- It works with both `for` and `while` loops.\n:::\n', 'match-statements.md': '+++\nslug = "match-statements"\ntitle = "Match Statements"\nsection = "Control Flow"\nsummary = "match selects cases using structural pattern matching."\ndoc_path = "/tutorial/controlflow.html#match-statements"\n+++\n\nStructural pattern matching lets a program choose a branch based on the shape of data. It is especially useful when commands, messages, or parsed data have a few known forms.\n\nA `case` pattern can both check constants and bind names. The move case checks the action and extracts `x` and `y` in one readable step.\n\nOrder matters because Python tries cases from top to bottom. Specific shapes should appear before broad fallback cases such as `_`.\n\n:::program\n```python\ncommand = {"action": "move", "x": 3, "y": 4}\n\nmatch command:\n case {"action": "move", "x": x, "y": y}:\n print(f"move to {x},{y}")\n case {"action": "quit"}:\n print("quit")\n case {"action": action}:\n print(f"unknown action: {action}")\n case _:\n print("invalid command")\n```\n:::\n\n:::cell\nUse `match` when the shape of a value is the decision. This command is a dictionary with an action and coordinates; the first case checks that shape and binds `x` and `y`.\n\n```python\ncommand = {"action": "move", "x": 3, "y": 4}\n\nmatch command:\n case {"action": "move", "x": x, "y": y}:\n print(f"move to {x},{y}")\n```\n\n```output\nmove to 3,4\n```\n:::\n\n:::cell\nOther cases describe other valid shapes. This complete fragment changes the command so the `quit` case is the first matching pattern.\n\n```python\ncommand = {"action": "quit"}\n\nmatch command:\n case {"action": "move", "x": x, "y": y}:\n print(f"move to {x},{y}")\n case {"action": "quit"}:\n print("quit")\n```\n\n```output\nquit\n```\n:::\n\n:::cell\nBroader patterns and the `_` catch-all belong after specific cases. This fragment extracts an unknown action before the final fallback would run.\n\n```python\ncommand = {"action": "jump"}\n\nmatch command:\n case {"action": "move", "x": x, "y": y}:\n print(f"move to {x},{y}")\n case {"action": "quit"}:\n print("quit")\n case {"action": action}:\n print(f"unknown action: {action}")\n case _:\n print("invalid command")\n```\n\n```output\nunknown action: jump\n```\n:::\n\n:::note\n- `match` compares structure, not just equality.\n- Patterns can bind names such as `x` and `y` while matching.\n- Put the catch-all `_` case last, because cases are tried from top to bottom.\n:::\n', 'metaclasses.md': '+++\nslug = "metaclasses"\ntitle = "Metaclasses"\nsection = "Classes"\nsummary = "A metaclass customizes how classes themselves are created."\ndoc_path = "/reference/datamodel.html#metaclasses"\nsee_also = [\n "classes",\n "inheritance-and-super",\n "special-methods",\n]\n+++\n\nA metaclass is the class of a class. Most Python code never needs one, but the syntax appears in frameworks that register, validate, or modify classes as they are created.\n\nThe `metaclass=` keyword in a class statement chooses the object that builds the class. This is advanced machinery; decorators and ordinary functions are usually simpler.\n\nUse metaclasses only when class creation itself is the problem being solved.\n\n:::program\n```python\nclass Tagged(type):\n def __new__(mcls, name, bases, namespace):\n namespace["tag"] = name.lower()\n return super().__new__(mcls, name, bases, namespace)\n\nclass Event(metaclass=Tagged):\n pass\n\nprint(Event.tag)\nprint(type(Event).__name__)\n```\n:::\n\n:::cell\nA metaclass customizes class creation. `__new__` receives the class name, bases, and namespace before the class object exists.\n\n```python\nclass Tagged(type):\n def __new__(mcls, name, bases, namespace):\n namespace["tag"] = name.lower()\n return super().__new__(mcls, name, bases, namespace)\n\nprint(Tagged.__name__)\n```\n\n```output\nTagged\n```\n:::\n\n:::cell\nThe `metaclass=` keyword applies that class-building rule. Here the metaclass adds a `tag` attribute to the new class.\n\n```python\nclass Event(metaclass=Tagged):\n pass\n\nprint(Event.tag)\nprint(type(Event).__name__)\n```\n\n```output\nevent\nTagged\n```\n:::\n\n:::note\n- Metaclasses customize class creation, not instance behavior directly.\n- Most code should prefer class decorators, functions, or ordinary inheritance.\n- You are most likely to meet metaclasses inside frameworks and ORMs.\n:::\n', 'modules.md': '+++\nslug = "modules"\ntitle = "Modules"\nsection = "Modules"\nsummary = "Modules organize code into namespaces and expose reusable definitions."\ndoc_path = "/tutorial/modules.html"\nsee_also = [\n "import-aliases",\n "packages",\n]\n+++\n\nModules organize Python code into files and namespaces. `import` executes a module once, stores it in Python\'s import cache, and gives your program access to its definitions.\n\nThis page focuses on import forms and module namespaces. Package layout, aliases, and dynamic imports have their own neighboring examples.\n\nUse module namespaces such as `math.sqrt` when the source of a name should stay visible. Use focused imports such as `from statistics import mean` when the imported name is clear at the call site.\n\n:::program\n```python\nimport math\nimport sys\nfrom statistics import mean\n\nradius = 3\narea = math.pi * radius ** 2\nprint(round(area, 2))\n\nscores = [8, 10, 9]\nprint(mean(scores))\n\nprint(math.__name__)\nprint("math" in sys.modules)\n```\n:::\n\n:::cell\nImporting a module gives access to its namespace. The `math.` prefix makes it clear where `pi` came from.\n\n```python\nimport math\n\nradius = 3\narea = math.pi * radius ** 2\nprint(round(area, 2))\n```\n\n```output\n28.27\n```\n:::\n\n:::cell\nA focused `from ... import ...` brings one definition into the current namespace. This keeps a common operation concise without importing every name.\n\n```python\nfrom statistics import mean\n\nscores = [8, 10, 9]\nprint(mean(scores))\n```\n\n```output\n9\n```\n:::\n\n:::cell\nModules are objects too. Their attributes include metadata such as `__name__`, which records the module\'s import name.\n\n```python\nprint(math.__name__)\n```\n\n```output\nmath\n```\n:::\n\n:::cell\nImported modules are cached in `sys.modules`. Later imports reuse the module object instead of executing the file again.\n\n```python\nimport sys\nprint("math" in sys.modules)\n```\n\n```output\nTrue\n```\n:::\n\n:::note\n- Prefer plain `import module` when the namespace improves readability.\n- Use focused imports for a small number of clear names.\n- Place imports near the top of the file.\n- Imports execute module top-level code once, then reuse the cached module object.\n:::\n', 'multiple-return-values.md': '+++\nslug = "multiple-return-values"\ntitle = "Multiple Return Values"\nsection = "Functions"\nsummary = "Python returns multiple values by returning a tuple and unpacking it."\ndoc_path = "/tutorial/datastructures.html#tuples-and-sequences"\n+++\n\nPython multiple return values are tuple return values with friendly syntax. `return a, b` creates one tuple containing two positions.\n\nMost callers unpack that tuple immediately. Good target names make the meaning of each returned position explicit.\n\nUse this for small, fixed groups of results. For larger records, a dataclass or named tuple usually communicates better.\n\n:::program\n```python\ndef divide_with_remainder(total, size):\n quotient = total // size\n remainder = total % size\n return quotient, remainder\n\nresult = divide_with_remainder(17, 5)\nprint(result)\n\nboxes, leftover = result\nprint(boxes)\nprint(leftover)\n```\n:::\n\n:::cell\nReturning values separated by commas returns one tuple. The tuple is visible if the caller stores the result directly.\n\n```python\ndef divide_with_remainder(total, size):\n quotient = total // size\n remainder = total % size\n return quotient, remainder\n\nresult = divide_with_remainder(17, 5)\nprint(result)\n```\n\n```output\n(3, 2)\n```\n:::\n\n:::cell\nCallers usually unpack the tuple immediately or soon after. The names at the call site document what each position means.\n\n```python\nboxes, leftover = result\nprint(boxes)\nprint(leftover)\n```\n\n```output\n3\n2\n```\n:::\n\n:::note\n- A comma creates a tuple; `return a, b` returns one tuple containing two values.\n- Unpacking at the call site gives each returned position a meaningful name.\n- Use a class-like record when the result has many fields.\n:::\n', 'mutability.md': '+++\nslug = "mutability"\ntitle = "Mutability"\nsection = "Data Model"\nsummary = "Some objects change in place, while others return new values."\ndoc_path = "/reference/datamodel.html#objects-values-and-types"\n+++\n\nObjects in Python can be mutable or immutable. Mutable objects such as lists and dictionaries can change in place, while immutable objects such as strings and tuples produce new values instead.\n\nNames can share one mutable object, so a change through one name is visible through another. This is powerful, but it is also the source of many beginner surprises.\n\nThe boundary matters across Python: `append()` mutates a list, string methods return new strings, and `sorted()` returns a new list while `list.sort()` mutates an existing one.\n\n:::program\n```python\nfirst = ["python"]\nsecond = first\nsecond.append("workers")\nprint(first)\nprint(second)\n\ntext = "python"\nupper_text = text.upper()\nprint(text)\nprint(upper_text)\n\nnumbers = [3, 1, 2]\nordered = sorted(numbers)\nprint(ordered)\nprint(numbers)\n```\n:::\n\n:::cell\nMutable objects can change in place. `first` and `second` point to the same list, so appending through one name changes the object seen through both names.\n\n```python\nfirst = ["python"]\nsecond = first\nsecond.append("workers")\nprint(first)\nprint(second)\n```\n\n```output\n[\'python\', \'workers\']\n[\'python\', \'workers\']\n```\n:::\n\n:::cell\nImmutable objects do not change in place. String methods such as `upper()` return a new string, leaving the original string unchanged.\n\n```python\ntext = "python"\nupper_text = text.upper()\nprint(text)\nprint(upper_text)\n```\n\n```output\npython\nPYTHON\n```\n:::\n\n:::cell\nSome APIs make the boundary explicit. `sorted()` returns a new list, while methods such as `append()` and `list.sort()` mutate an existing list.\n\n```python\nnumbers = [3, 1, 2]\nordered = sorted(numbers)\nprint(ordered)\nprint(numbers)\n```\n\n```output\n[1, 2, 3]\n[3, 1, 2]\n```\n:::\n\n:::note\n- Lists and dictionaries are mutable; strings and tuples are immutable.\n- Aliasing is useful, but copy mutable containers when independent changes are needed.\n- Pay attention to whether an operation mutates in place or returns a new value.\n:::\n', 'networking.md': '+++\nslug = "networking"\ntitle = "Networking"\nsection = "Standard Library"\nsummary = "Networking code exchanges bytes across explicit protocol boundaries."\ndoc_path = "/library/socket.html"\nexpected_output = "b\'ping\'\\nping\\n"\n+++\n\nNetworking code sends and receives bytes. Higher-level HTTP clients hide many details, but the core boundary is still explicit: text must be encoded before sending and decoded after receiving.\n\nThis example uses `socket.socketpair()` so it stays local and deterministic in ordinary Python. Real network clients often use `socket.create_connection()` or a higher-level HTTP library, but the same byte boundary applies.\n\nThe useful mental model is endpoint plus bytes plus cleanup. A socket connects two endpoints, transfers byte strings, and must be closed when the conversation is finished.\n\n:::program\n```python\nimport socket\n\nleft, right = socket.socketpair()\ntry:\n message = "ping"\n left.sendall(message.encode("utf-8"))\n data = right.recv(16)\n print(data)\n print(data.decode("utf-8"))\nfinally:\n left.close()\n right.close()\n```\n:::\n\n:::unsupported\nDynamic Workers do not provide arbitrary low-level sockets, and this app disables Dynamic Worker outbound access.\n\n```python\nleft, right = socket.socketpair()\nleft.sendall("ping".encode("utf-8"))\ndata = right.recv(16)\n```\n:::\n\n:::cell\nSockets exchange bytes. Encoding and decoding make the boundary between Python text and network data visible.\n\n```python\nimport socket\n\nleft, right = socket.socketpair()\ntry:\n message = "ping"\n left.sendall(message.encode("utf-8"))\n data = right.recv(16)\n print(data)\n print(data.decode("utf-8"))\nfinally:\n left.close()\n right.close()\n```\n\n```output\nb\'ping\'\nping\n```\n:::\n\n:::note\n- Network protocols move bytes, not Python `str` objects.\n- Close real sockets when finished, usually with a context manager or `finally` block.\n- Use high-level HTTP libraries for application HTTP unless socket-level control is the lesson.\n:::\n', 'newtype.md': '+++\nslug = "newtype"\ntitle = "NewType"\nsection = "Types"\nsummary = "NewType creates distinct static identities for runtime-compatible values."\ndoc_path = "/library/typing.html#typing.NewType"\nsee_also = [\n "type-aliases",\n "type-hints",\n "runtime-type-checks",\n]\n+++\n\n`NewType` creates a distinct static identity for a value that is represented by an existing runtime type. It is useful for IDs, units, and other values that should not be mixed accidentally.\n\nThe key boundary is static versus runtime behavior. A type checker can distinguish `UserId` from `OrderId`, but at runtime both values are plain integers.\n\nUse a type alias when you only want a clearer name for a shape. Use `NewType` when mixing two compatible shapes should be treated as a mistake by static analysis.\n\n:::program\n```python\nfrom typing import NewType\n\nUserId = NewType("UserId", int)\nOrderId = NewType("OrderId", int)\n\n\ndef load_user(user_id: UserId) -> str:\n return f"user {user_id}"\n\nuid = UserId(42)\noid = OrderId(42)\nprint(load_user(uid))\nprint(uid == oid)\nprint(type(uid).__name__)\nprint(UserId.__name__)\n```\n:::\n\n:::cell\n`NewType` helps type checkers distinguish values that share a runtime representation.\n\n```python\nfrom typing import NewType\n\nUserId = NewType("UserId", int)\nOrderId = NewType("OrderId", int)\n\n\ndef load_user(user_id: UserId) -> str:\n return f"user {user_id}"\n\nuid = UserId(42)\nprint(load_user(uid))\n```\n\n```output\nuser 42\n```\n:::\n\n:::cell\nAt runtime, a `NewType` value is the underlying value. It compares like that value and has the same runtime type.\n\n```python\noid = OrderId(42)\nprint(uid == oid)\nprint(type(uid).__name__)\n```\n\n```output\nTrue\nint\n```\n:::\n\n:::cell\nThe `NewType` constructor keeps a name for static tools and introspection.\n\n```python\nprint(UserId.__name__)\nprint(OrderId.__name__)\n```\n\n```output\nUserId\nOrderId\n```\n:::\n\n:::note\n- `NewType` helps type checkers distinguish values that share a runtime representation.\n- At runtime, the value is still the underlying type.\n- Use aliases for readability; use `NewType` for static separation.\n:::\n', 'none.md': '+++\nslug = "none"\ntitle = "None"\nsection = "Basics"\nsummary = "None represents expected absence, distinct from missing keys and errors."\ndoc_path = "/library/constants.html#None"\n+++\n\n`None` represents the absence of a value. It is the usual sentinel when a function has no result to return but the absence itself is meaningful.\n\nBecause `None` is a singleton, idiomatic Python checks it with `is None` or `is not None`. This avoids confusing identity with value equality.\n\nAbsence has several nearby shapes in Python. A function can return `None`, a dictionary lookup can supply a default for a missing key, and an invalid operation can raise an exception.\n\n:::program\n```python\nresult = None\nprint(result is None)\n\ndef find_score(name):\n if name == "Ada":\n return 10\n return None\n\nscore = find_score("Grace")\nprint(score is None)\n\nprofile = {"name": "Ada"}\nprint(profile.get("timezone", "UTC"))\n\ntry:\n int("python")\nexcept ValueError:\n print("invalid number")\n```\n:::\n\n:::cell\n`None` is Python\'s value for “nothing here.” Check it with `is None` because it is a singleton identity value.\n\n```python\nresult = None\nprint(result is None)\n```\n\n```output\nTrue\n```\n:::\n\n:::cell\nFunctions often return `None` when absence is expected and callers can continue. The function name and surrounding code should make that possibility clear.\n\n```python\ndef find_score(name):\n if name == "Ada":\n return 10\n return None\n\nscore = find_score("Grace")\nprint(score is None)\n```\n\n```output\nTrue\n```\n:::\n\n:::cell\nA missing dictionary key is another absence boundary. Use `get()` when the mapping can supply a default, and use exceptions for invalid operations that cannot produce a value.\n\n```python\nprofile = {"name": "Ada"}\nprint(profile.get("timezone", "UTC"))\n\ntry:\n int("python")\nexcept ValueError:\n print("invalid number")\n```\n\n```output\nUTC\ninvalid number\n```\n:::\n\n:::note\n- Use `is None` rather than `== None`; `None` is a singleton identity value.\n- Use `None` for expected absence that callers can test.\n- Use dictionary defaults for missing mapping keys and exceptions for invalid operations.\n:::\n', 'number-parsing.md': '+++\nslug = "number-parsing"\ntitle = "Number Parsing"\nsection = "Standard Library"\nsummary = "int() and float() parse text into numbers and raise ValueError on bad input."\ndoc_path = "/library/functions.html#int"\n+++\n\nParsing turns text into numeric values. `int()` parses whole-number text and `float()` parses decimal or scientific-notation text.\n\nInvalid numeric text raises `ValueError`. Catching that specific exception makes it clear that bad input is expected and recoverable.\n\nAfter parsing, the result is a number and can participate in arithmetic; before parsing, it is just text.\n\n:::program\n```python\nprint(int("42"))\nprint(float("3.5"))\n\ntry:\n int("python")\nexcept ValueError as error:\n print(type(error).__name__)\n```\n:::\n\n:::cell\nUse `int()` for whole numbers and `float()` for decimal text. Parsed values are real numbers, not strings.\n\n```python\nprint(int("42"))\nprint(float("3.5"))\n```\n\n```output\n42\n3.5\n```\n:::\n\n:::cell\nBad numeric text raises `ValueError`. Catch that specific exception when invalid input is part of the normal flow.\n\n```python\ntry:\n int("python")\nexcept ValueError as error:\n print(type(error).__name__)\n```\n\n```output\nValueError\n```\n:::\n\n:::note\n- `int()` and `float()` are constructors that also parse strings.\n- Catch `ValueError` when bad user input is expected and recoverable.\n:::\n', 'numbers.md': '+++\nslug = "numbers"\ntitle = "Numbers"\nsection = "Basics"\nsummary = "Python numbers include integers, floats, and complex values."\ndoc_path = "/library/stdtypes.html#numeric-types-int-float-complex"\nsee_also = [\n "literals",\n "operators",\n]\n+++\n\nPython\'s numeric model starts with `int`, `float`, and `complex`. Integers are arbitrary precision, floats are approximate double-precision values, and complex numbers carry real and imaginary parts.\n\nOperators encode different numeric questions. `/` means true division and returns a float, `//` means floor division, `%` gives the remainder, and `**` computes powers.\n\nUse rounding for display, not as a substitute for understanding floating-point approximation. Financial code usually needs `decimal.Decimal`, which is a separate precision topic.\n\n:::program\n```python\ncount = 10\nratio = 0.25\nz = 2 + 3j\n\nprint(count + 5)\nprint(count / 4)\nprint(count // 4)\nprint(count % 4)\nprint(2 ** 5)\nprint(z.real, z.imag)\nprint(0.1 + 0.2)\nprint(round(3.14159, 2))\n```\n:::\n\n:::cell\nPython has `int` for whole numbers and `float` for approximate real-valued arithmetic. True division with `/` returns a `float`, even when both inputs are integers.\n\n```python\ncount = 10\nratio = 0.25\n\nprint(count + 5)\nprint(count / 4)\nprint(ratio * 2)\n```\n\n```output\n15\n2.5\n0.5\n```\n:::\n\n:::cell\nFloor division and modulo are useful when you need quotient and remainder behavior. Powers use `**`, not `^`.\n\n```python\nprint(count // 4)\nprint(count % 4)\nprint(2 ** 5)\n```\n\n```output\n2\n2\n32\n```\n:::\n\n:::cell\nComplex numbers are built in. The literal suffix `j` marks the imaginary part.\n\n```python\nz = 2 + 3j\nprint(z.real, z.imag)\n```\n\n```output\n2.0 3.0\n```\n:::\n\n:::cell\nFloating-point values are approximate. Round for display when the exact binary representation is not the lesson.\n\n```python\nprint(0.1 + 0.2)\nprint(round(3.14159, 2))\n```\n\n```output\n0.30000000000000004\n3.14\n```\n:::\n\n:::note\n- Python\'s `int` has arbitrary precision; it grows as large as memory allows.\n- Python\'s `float` is approximate double-precision floating point.\n- Use `/` for true division and `//` for floor division.\n- Use `decimal.Decimal` when decimal precision is the domain requirement, not just display polish.\n:::\n', 'object-lifecycle.md': '+++\nslug = "object-lifecycle"\ntitle = "Object Lifecycle"\nsection = "Basics"\nsummary = "References keep objects alive until Python can reclaim them."\ndoc_path = "/reference/datamodel.html#objects-values-and-types"\n+++\n\nReferences keep objects alive until Python can reclaim them. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.\n\nUse it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.\n\nThe example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.\n\n:::program\n```python\nimport gc\n\nnames = []\nalias = names\nalias.append("Ada")\n\nprint(names is alias)\nprint(names)\n\nobject_id = id(names)\ndel alias\nprint(id(names) == object_id)\n\ndel names\nprint("object can be reclaimed")\ngc.collect()\n```\n:::\n\n:::cell\nUse `is` and `id()` to observe identity while two names refer to the same object.\n\n```python\nimport gc\n\nnames = []\nalias = names\nalias.append("Ada")\n\nprint(names is alias)\nprint(names)\n\nobject_id = id(names)\ndel alias\nprint(id(names) == object_id)\n\ndel names\nprint("object can be reclaimed")\ngc.collect()\n```\n\n```output\nTrue\n[\'Ada\']\nTrue\nobject can be reclaimed\n```\n:::\n\n:::note\n- Use `is` and `id()` to observe identity while two names refer to the same object.\n- Deleting a name removes one reference; it does not directly destroy the object if another reference still exists.\n- Python reclaims unreachable objects automatically, so programs usually manage ownership by controlling references.\n:::\n', 'operator-overloading.md': '+++\nslug = "operator-overloading"\ntitle = "Operator Overloading"\nsection = "Data Model"\nsummary = "Operator methods let objects define arithmetic and comparison syntax."\ndoc_path = "/reference/datamodel.html#emulating-numeric-types"\nsee_also = [\n "operators",\n "special-methods",\n "equality-and-identity",\n]\n+++\n\nOperator overloading lets a class define what expressions such as `a + b` mean for its objects. This is useful when the operation is part of the domain vocabulary.\n\nThe method should preserve the meaning readers expect from the operator. Vectors can add component by component; money can add amounts in the same currency; surprising overloads make code harder to trust.\n\nPython also has reflected methods such as `__radd__` for cases where the left operand does not know how to handle the right operand. That keeps mixed operations possible without making every type know every other type.\n\n:::program\n```python\nclass Vector:\n def __init__(self, x, y):\n self.x = x\n self.y = y\n\n def __add__(self, other):\n return Vector(self.x + other.x, self.y + other.y)\n\n def __eq__(self, other):\n return (self.x, self.y) == (other.x, other.y)\n\n def __repr__(self):\n return f"Vector({self.x}, {self.y})"\n\nprint(Vector(2, 3) + Vector(4, 5))\nprint(Vector(1, 1) == Vector(1, 1))\n```\n:::\n\n:::cell\n`__add__` defines how the `+` operator combines two objects.\n\n```python\nclass Vector:\n def __init__(self, x, y):\n self.x = x\n self.y = y\n\n def __add__(self, other):\n return Vector(self.x + other.x, self.y + other.y)\n\n def __repr__(self):\n return f"Vector({self.x}, {self.y})"\n\nprint(Vector(2, 3) + Vector(4, 5))\n```\n\n```output\nVector(6, 8)\n```\n:::\n\n:::cell\n`__eq__` defines value equality for `==`. Without it, user-defined objects compare by identity.\n\n```python\nclass Vector:\n def __init__(self, x, y):\n self.x = x\n self.y = y\n\n def __eq__(self, other):\n return (self.x, self.y) == (other.x, other.y)\n\nprint(Vector(1, 1) == Vector(1, 1))\n```\n\n```output\nTrue\n```\n:::\n\n:::cell\nA useful `__repr__` makes operator results inspectable while debugging.\n\n```python\nclass Vector:\n def __init__(self, x, y):\n self.x = x\n self.y = y\n\n def __add__(self, other):\n return Vector(self.x + other.x, self.y + other.y)\n\n def __repr__(self):\n return f"Vector({self.x}, {self.y})"\n\nprint(repr(Vector(2, 3) + Vector(4, 5)))\n```\n\n```output\nVector(6, 8)\n```\n:::\n\n:::note\n- Overload operators only when the operation is unsurprising.\n- Return `NotImplemented` when an operand type is unsupported.\n- Implement equality deliberately when value comparison matters.\n:::\n', 'operators.md': '+++\nslug = "operators"\ntitle = "Operators"\nsection = "Basics"\nsummary = "Operators combine, compare, and test values in expressions."\ndoc_path = "/reference/expressions.html#operator-precedence"\nsee_also = [\n "numbers",\n "conditionals",\n "assignment-expressions",\n "operator-overloading",\n]\n+++\n\nOperators are the punctuation and keywords that combine values into expressions. Some operators compute new values, some compare values, and some ask relationship questions such as membership or identity.\n\nThis page is the surface map. Focused examples explain the deeper behavior of numbers, booleans, conditions, sets, assignment expressions, and operator overloading.\n\nRead operators by the question they ask: arithmetic computes, comparison answers true or false, boolean operators combine truth values, membership searches a container, and specialized operators should only appear when the data shape calls for them.\n\n:::program\n```python\ncount = 10\nprint(count + 5)\nprint(count // 4)\nprint(count % 4)\nprint(2 ** 5)\n\nscore = 91\nprint(80 <= score < 100)\nprint(score == 100 or score >= 90)\nprint("py" in "python")\n\nflags = 0b0011\nprint(flags ^ 0b0101)\nprint(flags << 1)\n\nclass Scale:\n def __init__(self, value):\n self.value = value\n\n def __matmul__(self, other):\n return self.value * other.value\n\nprint(Scale(2) @ Scale(3))\n\nitems = ["a", "b"]\nif (size := len(items)) > 0:\n print(size)\n```\n:::\n\n:::cell\nArithmetic operators compute new values. Use `//` for floor division, `%` for remainder, and `**` for powers.\n\n```python\ncount = 10\nprint(count + 5)\nprint(count // 4)\nprint(count % 4)\nprint(2 ** 5)\n```\n\n```output\n15\n2\n2\n32\n```\n:::\n\n:::cell\nComparison operators produce booleans. Python comparisons can chain, which keeps range checks readable.\n\n```python\nscore = 91\nprint(80 <= score < 100)\nprint(score == 100 or score >= 90)\nprint("py" in "python")\n```\n\n```output\nTrue\nTrue\nTrue\n```\n:::\n\n:::cell\nBitwise operators work on integer bit patterns. They are useful for masks and flags, not ordinary boolean logic.\n\n```python\nflags = 0b0011\nprint(flags ^ 0b0101)\nprint(flags << 1)\n```\n\n```output\n6\n6\n```\n:::\n\n:::cell\nThe `@` operator is reserved for matrix-like multiplication and custom types that define `__matmul__`.\n\n```python\nclass Scale:\n def __init__(self, value):\n self.value = value\n\n def __matmul__(self, other):\n return self.value * other.value\n\nprint(Scale(2) @ Scale(3))\n```\n\n```output\n6\n```\n:::\n\n:::cell\nThe walrus operator `:=` assigns inside an expression. Use it when naming a value avoids repeating work in a condition.\n\n```python\nitems = ["a", "b"]\nif (size := len(items)) > 0:\n print(size)\n```\n\n```output\n2\n```\n:::\n\n:::note\n- Use the clearest operator for the question: arithmetic, comparison, boolean logic, membership, identity, or bitwise manipulation.\n- `and` and `or` short-circuit, so the right side may not run.\n- Operators have precedence; use parentheses when grouping would otherwise be hard to read.\n- Custom operator behavior should make an object feel more natural, not more clever.\n:::\n', 'overloads.md': '+++\nslug = "overloads"\ntitle = "Overloads"\nsection = "Types"\nsummary = "overload describes APIs whose return type depends on argument types."\ndoc_path = "/library/typing.html#typing.overload"\n+++\n\noverload describes APIs whose return type depends on argument types. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.\n\nUse it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.\n\nThe example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.\n\n:::program\n```python\nfrom typing import overload\n\n@overload\ndef double(value: int) -> int: ...\n\n@overload\ndef double(value: str) -> str: ...\n\ndef double(value):\n return value * 2\n\nprint(double(4))\nprint(double("ha"))\nprint(double.__name__)\n```\n:::\n\n:::cell\n`@overload` signatures are for static type checkers.\n\n```python\nfrom typing import overload\n\n@overload\ndef double(value: int) -> int: ...\n\n@overload\ndef double(value: str) -> str: ...\n\ndef double(value):\n return value * 2\n\nprint(double(4))\nprint(double("ha"))\nprint(double.__name__)\n```\n\n```output\n8\nhaha\ndouble\n```\n:::\n\n:::note\n- `@overload` signatures are for static type checkers.\n- The real implementation comes after the overload declarations.\n- Use overloads when a single runtime function has multiple precise static shapes.\n:::\n', 'packages.md': '+++\nslug = "packages"\ntitle = "Packages"\nsection = "Modules"\nsummary = "Packages organize modules into importable directories."\ndoc_path = "/tutorial/modules.html#packages"\n+++\n\nPackages are modules that can contain other modules. They let a project group related code behind dotted import paths such as `json.decoder` or `email.message`.\n\nAt runtime, importing a submodule gives Python a path through that package structure. In a project on disk, that structure is usually a directory with Python files and often an `__init__.py` file.\n\nUse packages when one module has grown into a small namespace of related modules. Keep module names boring and explicit so readers can tell where imported definitions come from.\n\n:::program\n```python\nimport importlib\nimport json\nimport json.decoder\n\nmodule = importlib.import_module("json.decoder")\n\nprint(json.__name__)\nprint(json.decoder.__name__)\nprint(module.JSONDecoder.__name__)\nprint(module is json.decoder)\n```\n:::\n\n:::cell\nA package is itself a module. The `json` package exposes a namespace that can contain submodules.\n\n```python\nimport json\n\nprint(json.__name__)\n```\n\n```output\njson\n```\n:::\n\n:::cell\nDotted imports name a path through a package. Importing `json.decoder` makes that submodule available under the package namespace.\n\n```python\nimport json.decoder\n\nprint(json.decoder.__name__)\nprint(json.decoder.JSONDecoder.__name__)\n```\n\n```output\njson.decoder\nJSONDecoder\n```\n:::\n\n:::cell\n`importlib.import_module()` imports by string. It is useful for plugin systems and dynamic imports, but ordinary `import` is clearer when the dependency is known.\n\n```python\nimport importlib\n\nmodule = importlib.import_module("json.decoder")\nprint(module is json.decoder)\n```\n\n```output\nTrue\n```\n:::\n\n:::note\n- A package is a module that can contain submodules.\n- Dotted imports should mirror a meaningful project structure.\n- Prefer ordinary imports unless the module name is truly dynamic.\n:::\n', 'paramspec.md': '+++\nslug = "paramspec"\ntitle = "ParamSpec"\nsection = "Types"\nsummary = "ParamSpec preserves callable parameter types through wrappers."\ndoc_path = "/library/typing.html#typing.ParamSpec"\n+++\n\nParamSpec preserves callable parameter types through wrappers. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.\n\nUse it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.\n\nThe example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.\n\n:::program\n```python\nfrom collections.abc import Callable\nfrom typing import ParamSpec, TypeVar\n\nP = ParamSpec("P")\nR = TypeVar("R")\n\n\ndef logged(func: Callable[P, R]) -> Callable[P, R]:\n def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:\n print("calling", func.__name__)\n return func(*args, **kwargs)\n return wrapper\n\n@logged\ndef add(left: int, right: int) -> int:\n return left + right\n\nprint(add(2, 3))\n```\n:::\n\n:::cell\n`ParamSpec` captures the parameters of a callable.\n\n```python\nfrom collections.abc import Callable\nfrom typing import ParamSpec, TypeVar\n\nP = ParamSpec("P")\nR = TypeVar("R")\n\n\ndef logged(func: Callable[P, R]) -> Callable[P, R]:\n def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:\n print("calling", func.__name__)\n return func(*args, **kwargs)\n return wrapper\n\n@logged\ndef add(left: int, right: int) -> int:\n return left + right\n\nprint(add(2, 3))\n```\n\n```output\ncalling add\n5\n```\n:::\n\n:::note\n- `ParamSpec` captures the parameters of a callable.\n- Wrappers can forward `*args` and `**kwargs` without erasing the original signature for type checkers.\n- This matters most for decorators.\n:::\n', 'partial-functions.md': '+++\nslug = "partial-functions"\ntitle = "Partial Functions"\nsection = "Functions"\nsummary = "functools.partial pre-fills arguments to make a more specific callable."\ndoc_path = "/library/functools.html#functools.partial"\n+++\n\nfunctools.partial pre-fills arguments to make a more specific callable. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.\n\nUse it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.\n\nThe example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.\n\n:::program\n```python\nfrom functools import partial\n\n\ndef apply_tax(rate, amount):\n return round(amount * (1 + rate), 2)\n\nvat = partial(apply_tax, 0.2)\nservice_tax = partial(apply_tax, rate=0.1)\n\nprint(vat(50))\nprint(service_tax(amount=80))\nprint(vat.func.__name__)\n```\n:::\n\n:::cell\nA partial object remembers some arguments.\n\n```python\nfrom functools import partial\n\n\ndef apply_tax(rate, amount):\n return round(amount * (1 + rate), 2)\n\nvat = partial(apply_tax, 0.2)\nservice_tax = partial(apply_tax, rate=0.1)\n\nprint(vat(50))\nprint(service_tax(amount=80))\nprint(vat.func.__name__)\n```\n\n```output\n60.0\n88.0\napply_tax\n```\n:::\n\n:::note\n- A partial object remembers some arguments.\n- The resulting callable can be passed where an ordinary function is expected.\n- Prefer a named function when the pre-filled behavior needs richer logic.\n:::\n', 'positional-only-parameters.md': '+++\nslug = "positional-only-parameters"\ntitle = "Positional-only Parameters"\nsection = "Functions"\nsummary = "Use / to mark parameters that callers must pass by position."\ndoc_path = "/tutorial/controlflow.html#special-parameters"\nsee_also = [\n "keyword-only-arguments",\n "functions",\n "args-and-kwargs",\n]\n+++\n\nA `/` in a function signature marks the parameters before it as positional-only. Callers must pass those arguments by position, not by keyword.\n\nThis is useful when parameter names are implementation details or when an API should match built-in functions that accept positional values.\n\nTogether, `/` and `*` let a signature draw clear boundaries: positional-only inputs, ordinary inputs, and keyword-only options.\n\n:::program\n```python\ndef scale(value, /, factor=2, *, clamp=False):\n result = value * factor\n if clamp:\n result = min(result, 10)\n return result\n\nprint(scale(4))\nprint(scale(4, factor=3))\nprint(scale(4, clamp=True))\n```\n:::\n\n:::cell\nParameters before `/` are positional-only. `value` is the main input, while `factor` remains an ordinary parameter that can be named.\n\n```python\ndef scale(value, /, factor=2, *, clamp=False):\n result = value * factor\n if clamp:\n result = min(result, 10)\n return result\n\nprint(scale(4))\nprint(scale(4, factor=3))\n```\n\n```output\n8\n12\n```\n:::\n\n:::cell\nParameters after `*` are keyword-only. That makes options such as `clamp` explicit at the call site.\n\n```python\nprint(scale(4, clamp=True))\n```\n\n```output\n8\n```\n:::\n\n:::note\n- `/` marks parameters before it as positional-only.\n- `*` marks parameters after it as keyword-only.\n- Use these markers when the call shape is part of the API design.\n:::\n', 'properties.md': '+++\nslug = "properties"\ntitle = "Properties"\nsection = "Classes"\nsummary = "@property keeps attribute syntax while adding computation or validation."\ndoc_path = "/library/functions.html#property"\n+++\n\nProperties let a class keep a simple attribute-style API while running code behind the scenes. Callers write `box.area`, but the class can compute the value from current state.\n\nA property setter can validate assignment without changing the public spelling of the attribute. This is the boundary: plain attributes are enough for plain data, while properties are for computed or protected data.\n\nUse properties for cheap, attribute-like operations. Expensive work or actions with side effects should usually remain explicit methods.\n\n:::program\n```python\nclass Rectangle:\n def __init__(self, width, height):\n self.width = width\n self.height = height\n\n @property\n def area(self):\n return self.width * self.height\n\n @property\n def width(self):\n return self._width\n\n @width.setter\n def width(self, value):\n if value <= 0:\n raise ValueError("width must be positive")\n self._width = value\n\nbox = Rectangle(3, 4)\nprint(box.area)\n\nbox.width = 5\nprint(box.area)\n\ntry:\n box.width = 0\nexcept ValueError as error:\n print(error)\n```\n:::\n\n:::cell\nA read-only property exposes computed data through attribute access. `area` stays current because it is calculated from `width` and `height` each time it is read.\n\n```python\nclass Rectangle:\n def __init__(self, width, height):\n self.width = width\n self.height = height\n\n @property\n def area(self):\n return self.width * self.height\n\n @property\n def width(self):\n return self._width\n\n @width.setter\n def width(self, value):\n if value <= 0:\n raise ValueError("width must be positive")\n self._width = value\n\nbox = Rectangle(3, 4)\nprint(box.area)\n```\n\n```output\n12\n```\n:::\n\n:::cell\nA setter lets assignment keep normal attribute syntax while the class validates or normalizes the value.\n\n```python\nbox.width = 5\nprint(box.area)\n```\n\n```output\n20\n```\n:::\n\n:::cell\nValidation belongs inside the class when every caller should obey the same rule. Invalid assignment raises an exception at the boundary.\n\n```python\ntry:\n box.width = 0\nexcept ValueError as error:\n print(error)\n```\n\n```output\nwidth must be positive\n```\n:::\n\n:::note\n- Properties let APIs start simple and grow validation or computation later.\n- Callers access a property like an attribute, not like a method.\n- Use methods instead when work is expensive or action-like.\n:::\n', 'protocols.md': '+++\nslug = "protocols"\ntitle = "Protocols"\nsection = "Types"\nsummary = "Protocol describes required behavior for structural typing."\ndoc_path = "/library/typing.html#typing.Protocol"\nsee_also = [\n "type-hints",\n "classes",\n "inheritance-and-super",\n]\n+++\n\n`Protocol` describes the methods or attributes an object must provide. It exists for structural typing: if an object has the right shape, type checkers can treat it as compatible.\n\nThis is different from inheritance. Inheritance says a class is explicitly derived from a parent; a protocol says callers only need a particular behavior.\n\nAt runtime, ordinary method lookup still applies. Protocols are mainly for static analysis, documentation, and API boundaries.\n\n:::program\n```python\nfrom typing import Protocol\n\nclass Greeter(Protocol):\n def greet(self) -> str:\n ...\n\nclass Person:\n def __init__(self, name):\n self.name = name\n\n def greet(self):\n return f"hello {self.name}"\n\n\ndef welcome(greeter: Greeter):\n print(greeter.greet())\n\nwelcome(Person("Ada"))\nprint(Greeter.__name__)\n```\n:::\n\n:::cell\nA protocol names required behavior. The ellipsis marks the method body as intentionally unspecified, similar to an interface declaration.\n\n```python\nfrom typing import Protocol\n\nclass Greeter(Protocol):\n def greet(self) -> str:\n ...\n\nprint(Greeter.__name__)\n```\n\n```output\nGreeter\n```\n:::\n\n:::cell\nA class can satisfy the protocol without inheriting from it. `Person` has a compatible `greet()` method, so it has the right shape for static type checkers.\n\n```python\nclass Person:\n def __init__(self, name):\n self.name = name\n\n def greet(self):\n return f"hello {self.name}"\n\nprint(Person("Ada").greet())\n```\n\n```output\nhello Ada\n```\n:::\n\n:::cell\nUse the protocol as an annotation at the API boundary. The function only cares that the object can greet; it does not care about the concrete class.\n\n```python\ndef welcome(greeter: Greeter):\n print(greeter.greet())\n\nwelcome(Person("Ada"))\n```\n\n```output\nhello Ada\n```\n:::\n\n:::note\n- Protocols are for structural typing: compatibility by shape rather than explicit inheritance.\n- Type checkers understand protocols; normal runtime method calls still do the work.\n- Prefer inheritance when shared implementation matters, and protocols when only required behavior matters.\n:::\n', 'recursion.md': '+++\nslug = "recursion"\ntitle = "Recursion"\nsection = "Functions"\nsummary = "Recursive functions solve nested problems by calling themselves on smaller pieces."\ndoc_path = "/tutorial/controlflow.html#defining-functions"\n+++\n\nA recursive function calls itself to solve a smaller piece of the same problem. Recursion exists for data that is naturally nested: trees, menus, expression nodes, and directory-like structures.\n\nEvery recursive function needs a base case that can be answered directly. The recursive case must move toward that base case by passing a smaller part of the data.\n\nPrefer loops for simple repetition over a flat sequence. Prefer recursion when the data shape is recursive too.\n\n:::program\n```python\ntree = {\n "value": 1,\n "children": [\n {"value": 2, "children": []},\n {"value": 3, "children": [{"value": 4, "children": []}]},\n ],\n}\n\ndef total(node):\n subtotal = node["value"]\n for child in node["children"]:\n subtotal += total(child)\n return subtotal\n\nprint(total({"value": 2, "children": []}))\nprint(total(tree))\n```\n:::\n\n:::cell\nA leaf node is the base case. It has no children, so the function can return its own value without making another recursive call.\n\n```python\ndef total(node):\n subtotal = node["value"]\n for child in node["children"]:\n subtotal += total(child)\n return subtotal\n\nprint(total({"value": 2, "children": []}))\n```\n\n```output\n2\n```\n:::\n\n:::cell\nA non-leaf node solves the same problem for each child, then combines those smaller totals with its own value.\n\n```python\ntree = {\n "value": 1,\n "children": [\n {"value": 2, "children": []},\n {"value": 3, "children": [{"value": 4, "children": []}]},\n ],\n}\n\nprint(total(tree))\n```\n\n```output\n10\n```\n:::\n\n:::note\n- Every recursive function needs a base case that stops the calls.\n- Recursion fits nested data better than flat repetition.\n- Python limits recursion depth, so loops are often better for very deep or simple repetition.\n:::\n', 'regular-expressions.md': '+++\nslug = "regular-expressions"\ntitle = "Regular Expressions"\nsection = "Text"\nsummary = "The re module searches and extracts text using regular expressions."\ndoc_path = "/library/re.html"\n+++\n\nRegular expressions are a compact language for searching and extracting text patterns. Python\'s `re` module provides the standard interface.\n\nUse regex when the pattern has structure: repeated records, alternatives, optional parts, or pieces you want to capture. Prefer ordinary string methods for simple substring checks because simpler code is easier to maintain.\n\nThis page is a first regex pass, not the whole language. It focuses on raw pattern strings, capturing groups, repeated matches, and the boundary where a string method is enough.\n\n:::program\n```python\nimport re\n\ntext = "Ada: 10, Grace: 9"\npattern = r"([A-Za-z]+): (\\d+)"\n\nfor name, score in re.findall(pattern, text):\n print(name, int(score))\n\nmatch = re.search(r"Grace: (\\d+)", text)\nprint(match.group(1))\nprint("Grace" in text)\n```\n:::\n\n:::cell\nRaw strings keep backslashes readable in regex patterns. Capturing groups return just the pieces inside parentheses.\n\n```python\nimport re\n\ntext = "Ada: 10, Grace: 9"\npattern = r"([A-Za-z]+): (\\d+)"\n\nfor name, score in re.findall(pattern, text):\n print(name, int(score))\n```\n\n```output\nAda 10\nGrace 9\n```\n:::\n\n:::cell\n`re.search()` finds the first match. A match object exposes captured groups by position.\n\n```python\nmatch = re.search(r"Grace: (\\d+)", text)\nprint(match.group(1))\n```\n\n```output\n9\n```\n:::\n\n:::cell\nFor a simple substring check, ordinary string membership is clearer than regex.\n\n```python\nprint("Grace" in text)\n```\n\n```output\nTrue\n```\n:::\n\n:::note\n- Use raw strings for regex patterns so backslashes are easier to read.\n- Use capturing groups when the point is extraction, not just matching.\n- Reach for string methods before regex when the pattern is simple.\n:::\n', 'runtime-type-checks.md': '+++\nslug = "runtime-type-checks"\ntitle = "Runtime Type Checks"\nsection = "Types"\nsummary = "type, isinstance, and issubclass inspect runtime relationships."\ndoc_path = "/library/functions.html#isinstance"\nsee_also = [\n "type-hints",\n "protocols",\n "casts-and-any",\n]\n+++\n\nRuntime type checks inspect real objects while the program is running. They are different from type hints, which mostly guide tools before the program runs.\n\nUse `type()` when the exact class matters, `isinstance()` when subclasses should count, and `issubclass()` when checking class relationships. Most APIs prefer behavior over type checks, but runtime checks are useful at input boundaries.\n\nDo not turn every function into a wall of `isinstance()` calls. If the code only needs an object that can perform an operation, duck typing or a protocol may be clearer.\n\n:::program\n```python\nclass Animal:\n pass\n\nclass Dog(Animal):\n pass\n\npet = Dog()\n\nprint(type(pet).__name__)\nprint(type(pet) is Animal)\nprint(isinstance(pet, Animal))\nprint(issubclass(Dog, Animal))\n```\n:::\n\n:::cell\n`type()` reports the exact runtime class. A `Dog` instance is not exactly an `Animal` instance.\n\n```python\nclass Animal:\n pass\n\nclass Dog(Animal):\n pass\n\npet = Dog()\nprint(type(pet).__name__)\nprint(type(pet) is Animal)\n```\n\n```output\nDog\nFalse\n```\n:::\n\n:::cell\n`isinstance()` accepts subclasses, which is usually what API boundaries want.\n\n```python\nprint(isinstance(pet, Dog))\nprint(isinstance(pet, Animal))\n```\n\n```output\nTrue\nTrue\n```\n:::\n\n:::cell\n`issubclass()` checks class relationships rather than individual objects.\n\n```python\nprint(issubclass(Dog, Animal))\n```\n\n```output\nTrue\n```\n:::\n\n:::note\n- `type()` is exact; `isinstance()` follows inheritance.\n- Runtime checks inspect objects, not static annotations.\n- Prefer behavior, protocols, or clear validation over scattered type checks.\n:::\n', 'scope-global-nonlocal.md': '+++\nslug = "scope-global-nonlocal"\ntitle = "Global and Nonlocal"\nsection = "Functions"\nsummary = "global and nonlocal choose which outer binding assignment should update."\ndoc_path = "/reference/simple_stmts.html#the-global-statement"\nsee_also = [\n "variables",\n "closures",\n "functions",\n]\n+++\n\nAssignment normally creates or updates a local name inside the current function. `global` and `nonlocal` are explicit escape hatches for rebinding names outside that local scope.\n\nUse `nonlocal` when an inner function should update a name in an enclosing function. Use `global` rarely; passing values and returning results is usually clearer.\n\nThese statements affect name binding, not object mutation. Mutating a shared list is different from rebinding the name itself.\n\n:::program\n```python\ncount = 0\n\ndef bump_global():\n global count\n count += 1\n\nbump_global()\nprint(count)\n\n\ndef make_counter():\n total = 0\n def bump():\n nonlocal total\n total += 1\n return total\n return bump\n\ncounter = make_counter()\nprint(counter())\nprint(counter())\n```\n:::\n\n:::cell\n`global` tells assignment to update a module-level binding. Without it, `count += 1` would try to assign a local `count`.\n\n```python\ncount = 0\n\ndef bump_global():\n global count\n count += 1\n\nbump_global()\nprint(count)\n```\n\n```output\n1\n```\n:::\n\n:::cell\n`nonlocal` tells assignment to update a binding in the nearest enclosing function scope. This is useful for small closures that keep state.\n\n```python\ndef make_counter():\n total = 0\n def bump():\n nonlocal total\n total += 1\n return total\n return bump\n\ncounter = make_counter()\nprint(counter())\nprint(counter())\n```\n\n```output\n1\n2\n```\n:::\n\n:::note\n- Assignment inside a function is local unless declared otherwise.\n- Prefer `nonlocal` for closure state and avoid `global` unless module state is truly intended.\n- Passing values and returning results is usually easier to test than rebinding outer names.\n:::\n', 'sentinel-iteration.md': '+++\nslug = "sentinel-iteration"\ntitle = "Sentinel Iteration"\nsection = "Iteration"\nsummary = "iter(callable, sentinel) repeats calls until a marker value appears."\ndoc_path = "/library/functions.html#iter"\n+++\n\niter(callable, sentinel) repeats calls until a marker value appears. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.\n\nUse it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.\n\nThe example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.\n\n:::program\n```python\nlines = iter(["alpha", "beta", ""])\n\ndef read_line():\n return next(lines)\n\nfor line in iter(read_line, ""):\n print(line.upper())\n```\n:::\n\n:::cell\nA zero-argument callable produces one value at a time.\n\n```python\nlines = iter(["alpha", "beta", ""])\n\ndef read_line():\n return next(lines)\n\nfor line in iter(read_line, ""):\n print(line.upper())\n```\n\n```output\nALPHA\nBETA\n```\n:::\n\n:::note\n- A zero-argument callable produces one value at a time.\n- The sentinel value stops the loop without appearing in the output.\n- This form is useful for repeated reads from files, sockets, or queues.\n:::\n', 'sets.md': '+++\nslug = "sets"\ntitle = "Sets"\nsection = "Collections"\nsummary = "Sets store unique values and make membership checks explicit."\ndoc_path = "/tutorial/datastructures.html#sets"\n+++\n\nSets store unique hashable values. Use them when membership and de-duplication matter more than order.\n\nA list can answer membership with `in`, but a set communicates that membership is the main operation. Set algebra then expresses how groups relate to each other.\n\nBecause sets are unordered, examples often wrap output in `sorted()` so the display is deterministic.\n\n:::program\n```python\nlanguages = ["python", "go", "python"]\nunique_languages = set(languages)\nprint(sorted(unique_languages))\n\nallowed = {"python", "rust"}\nprint("python" in allowed)\nprint("ruby" in allowed)\n\ncompiled = {"go", "rust"}\nprint(sorted(allowed | compiled))\nprint(sorted(allowed & compiled))\nprint(sorted(allowed - compiled))\n```\n:::\n\n:::cell\nCreating a set removes duplicates. Keep a list when order and repeated values matter; convert to a set when uniqueness is the point.\n\n```python\nlanguages = ["python", "go", "python"]\nunique_languages = set(languages)\nprint(sorted(unique_languages))\n```\n\n```output\n[\'go\', \'python\']\n```\n:::\n\n:::cell\nMembership checks are the everyday set operation. A list can also use `in`, but a set says that membership is central to the data shape.\n\n```python\nallowed = {"python", "rust"}\nprint("python" in allowed)\nprint("ruby" in allowed)\n```\n\n```output\nTrue\nFalse\n```\n:::\n\n:::cell\nUnion, intersection, and difference describe relationships between groups without manual loops.\n\n```python\ncompiled = {"go", "rust"}\nprint(sorted(allowed | compiled))\nprint(sorted(allowed & compiled))\nprint(sorted(allowed - compiled))\n```\n\n```output\n[\'go\', \'python\', \'rust\']\n[\'rust\']\n[\'python\']\n```\n:::\n\n:::note\n- Use lists when order and repeated values matter.\n- Use sets when uniqueness and membership are the main operations.\n- Prefer lists when order or repeated values are part of the meaning.\n- Sets are unordered, so sort them when examples need deterministic display.\n:::\n', 'slices.md': '+++\nslug = "slices"\ntitle = "Slices"\nsection = "Collections"\nsummary = "Slices copy meaningful ranges from ordered sequences."\ndoc_path = "/tutorial/introduction.html#lists"\n+++\n\nSlicing reads a range from an ordered sequence with `start:stop:step`. It exists because Python code often needs a meaningful piece of a sequence: a page, a prefix, a tail, a stride, or a reversed view.\n\nThe stop index is excluded. That convention makes lengths and adjacent ranges line up: `items[:3]` and `items[3:]` split a sequence without overlap.\n\nSlices return new sequence objects for built-in lists and strings. Use indexing for one item; use slicing when the result should still be a sequence.\n\n:::program\n```python\nletters = ["a", "b", "c", "d", "e", "f"]\nfirst_page = letters[:3]\nrest = letters[3:]\nprint(first_page)\nprint(rest)\n\nmiddle = letters[1:5]\nevery_other = letters[::2]\nreversed_letters = letters[::-1]\nprint(middle)\nprint(every_other)\nprint(reversed_letters)\nprint(letters)\n```\n:::\n\n:::cell\nOmitted bounds mean “from the beginning” or “through the end.” Because the stop index is excluded, adjacent slices split a sequence cleanly.\n\n```python\nletters = ["a", "b", "c", "d", "e", "f"]\nfirst_page = letters[:3]\nrest = letters[3:]\nprint(first_page)\nprint(rest)\n```\n\n```output\n[\'a\', \'b\', \'c\']\n[\'d\', \'e\', \'f\']\n```\n:::\n\n:::cell\nUse `start:stop` for a middle range and `step` when you want to skip or walk backward. These operations return new lists; the original list is unchanged.\n\n```python\nmiddle = letters[1:5]\nevery_other = letters[::2]\nreversed_letters = letters[::-1]\nprint(middle)\nprint(every_other)\nprint(reversed_letters)\nprint(letters)\n```\n\n```output\n[\'b\', \'c\', \'d\', \'e\']\n[\'a\', \'c\', \'e\']\n[\'f\', \'e\', \'d\', \'c\', \'b\', \'a\']\n[\'a\', \'b\', \'c\', \'d\', \'e\', \'f\']\n```\n:::\n\n:::note\n- Slice stop indexes are excluded, so adjacent ranges compose cleanly.\n- Omitted bounds mean the beginning or end of the sequence.\n- A negative step walks backward; `[::-1]` is a common reversed-copy idiom.\n:::\n', 'sorting.md': '+++\nslug = "sorting"\ntitle = "Sorting"\nsection = "Collections"\nsummary = "sorted returns a new ordered list and key functions choose the sort value."\ndoc_path = "/howto/sorting.html"\n+++\n\n`sorted()` accepts any iterable and returns a new list. The original collection is left untouched, which makes `sorted()` useful in expressions and pipelines.\n\nUse `key=` to say what value should be compared for each item. This is the idiomatic way to sort records, tuples, dictionaries, and objects by a field.\n\nUse `reverse=True` for descending order. Use `list.sort()` instead when you intentionally want to mutate an existing list in place.\n\n:::program\n```python\nnames = ["Guido", "Ada", "Grace"]\nprint(sorted(names))\nprint(names)\n\nusers = [\n {"name": "Ada", "score": 10},\n {"name": "Guido", "score": 8},\n {"name": "Grace", "score": 10},\n]\nranked = sorted(users, key=lambda user: user["score"], reverse=True)\nprint([user["name"] for user in ranked])\n\nusers.sort(key=lambda user: user["name"])\nprint([user["name"] for user in users])\n```\n:::\n\n:::cell\n`sorted()` returns a new list. Printing the original list afterward shows that the input order did not change.\n\n```python\nnames = ["Guido", "Ada", "Grace"]\nprint(sorted(names))\nprint(names)\n```\n\n```output\n[\'Ada\', \'Grace\', \'Guido\']\n[\'Guido\', \'Ada\', \'Grace\']\n```\n:::\n\n:::cell\nA key function computes the value to compare. Here the records are sorted by score, highest first, and the output shows the resulting order.\n\n```python\nusers = [\n {"name": "Ada", "score": 10},\n {"name": "Guido", "score": 8},\n {"name": "Grace", "score": 10},\n]\nranked = sorted(users, key=lambda user: user["score"], reverse=True)\nprint([user["name"] for user in ranked])\n```\n\n```output\n[\'Ada\', \'Grace\', \'Guido\']\n```\n:::\n\n:::cell\n`list.sort()` sorts the list in place. Use it when mutation is the point and no separate sorted copy is needed.\n\n```python\nusers.sort(key=lambda user: user["name"])\nprint([user["name"] for user in users])\n```\n\n```output\n[\'Ada\', \'Grace\', \'Guido\']\n```\n:::\n\n:::note\n- `sorted()` makes a new list; `list.sort()` mutates an existing list.\n- `key=` should return the value Python compares for each item.\n- Python\'s sort is stable, so equal keys keep their original relative order.\n:::\n', 'special-methods.md': '+++\nslug = "special-methods"\ntitle = "Special Methods"\nsection = "Data Model"\nsummary = "Special methods connect your objects to Python syntax and built-ins."\ndoc_path = "/reference/datamodel.html#special-method-names"\n+++\n\nSpecial methods, often called dunder methods, connect user-defined classes to Python syntax and built-ins such as len(), iter(), and repr().\n\nImplementing these methods lets your objects participate in Python protocols rather than forcing callers to learn custom method names for common operations.\n\nGood special methods make objects feel boring in the best way: they work with the language features Python programmers already know.\n\n:::program\n```python\nclass Bag:\n def __init__(self, items):\n self.items = list(items)\n\n def __len__(self):\n return len(self.items)\n\n def __iter__(self):\n return iter(self.items)\n\n def __repr__(self):\n return f"Bag({self.items!r})"\n\nbag = Bag(["a", "b"])\nprint(len(bag))\nprint(list(bag))\nprint(bag)\n```\n:::\n\n:::cell\nStart with a normal class that stores its data. Special methods build on ordinary instance state.\n\n```python\nclass Bag:\n def __init__(self, items):\n self.items = list(items)\n\nbag = Bag(["a", "b"])\nprint(bag.items)\n```\n\n```output\n[\'a\', \'b\']\n```\n:::\n\n:::cell\nImplement `__len__` to let `len()` ask the object for its size using Python\'s standard protocol.\n\n```python\nclass Bag:\n def __init__(self, items):\n self.items = list(items)\n\n def __len__(self):\n return len(self.items)\n\nbag = Bag(["a", "b"])\nprint(len(bag))\n```\n\n```output\n2\n```\n:::\n\n:::cell\nImplement `__iter__` to make the object iterable. Then tools such as `list()` can consume it without a custom method name.\n\n```python\nclass Bag:\n def __init__(self, items):\n self.items = list(items)\n\n def __len__(self):\n return len(self.items)\n\n def __iter__(self):\n return iter(self.items)\n\nbag = Bag(["a", "b"])\nprint(list(bag))\n```\n\n```output\n[\'a\', \'b\']\n```\n:::\n\n:::cell\nImplement `__repr__` to give the object a useful developer-facing representation when it is printed or inspected.\n\n```python\nclass Bag:\n def __init__(self, items):\n self.items = list(items)\n\n def __len__(self):\n return len(self.items)\n\n def __iter__(self):\n return iter(self.items)\n\n def __repr__(self):\n return f"Bag({self.items!r})"\n\nbag = Bag(["a", "b"])\nprint(bag)\n```\n\n```output\nBag([\'a\', \'b\'])\n```\n:::\n\n:::note\n- Dunder methods are looked up by Python\'s data model protocols.\n- Implement the smallest protocol that makes your object feel native.\n:::\n', 'string-formatting.md': '+++\nslug = "string-formatting"\ntitle = "String Formatting"\nsection = "Text"\nsummary = "f-strings turn values into readable text at the point of use."\ndoc_path = "/tutorial/inputoutput.html#formatted-string-literals"\n+++\n\nFormatted string literals, or f-strings, exist because programs constantly need to turn values into human-readable text. They keep the expression next to the words it explains.\n\nFormat specifications after `:` control presentation details such as width, alignment, padding, and precision. This separates the value being computed from the way it should be displayed.\n\nUse f-strings for most new formatting code. They relate directly to expressions: anything inside braces is evaluated, then formatted into the surrounding string.\n\n:::program\n```python\nname = "Ada"\nscore = 9.5\nrank = 1\n\nmessage = f"{name} scored {score}"\nprint(message)\n\nrow = f"{rank:>2} | {name:<8} | {score:05.1f}"\nprint(row)\n\nprint(f"{score = }")\n```\n:::\n\n:::cell\nAn f-string evaluates expressions inside braces and inserts their string form into the surrounding text. This is clearer than joining several converted values by hand.\n\n```python\nname = "Ada"\nscore = 9.5\nrank = 1\n\nmessage = f"{name} scored {score}"\nprint(message)\n```\n\n```output\nAda scored 9.5\n```\n:::\n\n:::cell\nFormat specifications after `:` control display without changing the underlying values. Here the rank is right-aligned, the name is left-aligned, and the score is padded to one decimal place.\n\n```python\nrow = f"{rank:>2} | {name:<8} | {score:05.1f}"\nprint(row)\n```\n\n```output\n 1 | Ada | 009.5\n```\n:::\n\n:::cell\nThe debug form with `=` is useful while learning or logging because it prints both the expression and the value.\n\n```python\nprint(f"{score = }")\n```\n\n```output\nscore = 9.5\n```\n:::\n\n:::note\n- Use `f"..."` strings for most new formatting code.\n- Expressions inside braces are evaluated before formatting.\n- Format specifications after `:` control alignment, width, padding, and precision.\n:::\n', 'strings.md': '+++\nslug = "strings"\ntitle = "Strings"\nsection = "Text"\nsummary = "Strings are immutable Unicode text sequences."\ndoc_path = "/library/stdtypes.html#text-sequence-type-str"\n+++\n\nPython strings are immutable Unicode text sequences. A `str` stores text as Unicode code points, so it can represent English, Thai, accented letters, emoji, and ordinary ASCII with the same type.\n\nUnicode matters because text length and byte length are different questions. The English word `"hello"` uses five code points and five UTF-8 bytes because ASCII characters encode as one byte each. The Thai greeting `"สวัสดี"` has six code points but needs eighteen UTF-8 bytes.\n\nUse `str` when you mean text, and encode to `bytes` only at boundaries such as files, network protocols, and binary APIs. String operations such as `upper()` and `strip()` return new strings instead of changing the original.\n\n:::program\n```python\nenglish = "hello"\nthai = "สวัสดี"\n\nfor label, word in [("English", english), ("Thai", thai)]:\n print(label, word, len(word), len(word.encode("utf-8")))\n\nprint(thai[0])\nprint([hex(ord(char)) for char in thai[:2]])\n\ntext = " café "\nclean = text.strip()\nprint(clean)\nprint(clean.upper())\nprint(clean.encode("utf-8"))\n```\n:::\n\n:::cell\nCompare an English greeting with a Thai greeting. Both are Python `str` values, but UTF-8 uses one byte for each ASCII code point and multiple bytes for many non-ASCII code points.\n\n```python\nenglish = "hello"\nthai = "สวัสดี"\n\nfor label, word in [("English", english), ("Thai", thai)]:\n print(label, word, len(word), len(word.encode("utf-8")))\n```\n\n```output\nEnglish hello 5 5\nThai สวัสดี 6 18\n```\n:::\n\n:::cell\nIndexing and iteration work with Unicode code points, not encoded bytes. `ord()` returns the integer code point, which is often displayed in hexadecimal when teaching text encoding.\n\n```python\nprint(thai[0])\nprint([hex(ord(char)) for char in thai[:2]])\n```\n\n```output\nส\n[\'0xe2a\', \'0xe27\']\n```\n:::\n\n:::cell\nString methods return new strings because strings are immutable. Encoding turns text into bytes when another system needs a byte representation.\n\n```python\ntext = " café "\nclean = text.strip()\nprint(clean)\nprint(clean.upper())\nprint(clean.encode("utf-8"))\n```\n\n```output\ncafé\nCAFÉ\nb\'caf\\xc3\\xa9\'\n```\n:::\n\n:::note\n- Use `str` for text and `bytes` for binary data.\n- `len(text)` counts Unicode code points; `len(text.encode("utf-8"))` counts encoded bytes.\n- ASCII text is a useful baseline because each ASCII code point is one UTF-8 byte.\n- String methods return new strings because strings are immutable.\n- User-visible “characters” can be more subtle than code points; combining marks and emoji sequences may need specialized text handling.\n:::\n', 'subprocesses.md': '+++\nslug = "subprocesses"\ntitle = "Subprocesses"\nsection = "Standard Library"\nsummary = "subprocess runs external commands with explicit arguments and captured outputs."\ndoc_path = "/library/subprocess.html"\nexpected_output = "child process\\n0\\n"\n+++\n\n`subprocess` is the standard boundary for running external commands. It starts another program, waits for it, and gives you a result object with the exit code and captured output.\n\nUse a list of arguments when possible, capture output when the parent program needs to inspect it, and treat a non-zero return code as a failure. The same ideas apply whether the child program is Python, Git, a compiler, or another command-line tool.\n\nThe important boundary is between Python objects and the operating system process table. Python prepares arguments and environment, then the child program runs independently and reports back through streams and an exit status.\n\n:::program\n```python\nimport subprocess\nimport sys\n\nresult = subprocess.run(\n [sys.executable, "-c", "print(\'child process\')"],\n text=True,\n capture_output=True,\n check=True,\n)\n\nprint(result.stdout.strip())\nprint(result.returncode)\n```\n:::\n\n:::unsupported\nDynamic Workers do not provide child processes.\n\n```python\nresult = subprocess.run(\n [sys.executable, "-c", "print(\'child process\')"],\n text=True,\n capture_output=True,\n check=True,\n)\n```\n:::\n\n:::cell\n`subprocess.run()` starts a child process and waits for it. `capture_output=True` stores the child\'s standard output and error streams on the result object.\n\n```python\nimport subprocess\nimport sys\n\nresult = subprocess.run(\n [sys.executable, "-c", "print(\'child process\')"],\n text=True,\n capture_output=True,\n check=True,\n)\n\nprint(result.stdout.strip())\nprint(result.returncode)\n```\n\n```output\nchild process\n0\n```\n:::\n\n:::note\n- Use a list of arguments instead of shell strings when possible.\n- Capture output when the parent program needs to inspect it.\n- `check=True` turns non-zero exits into exceptions.\n:::\n', 'testing.md': '+++\nslug = "testing"\ntitle = "Testing"\nsection = "Standard Library"\nsummary = "Tests make expected behavior executable and repeatable."\ndoc_path = "/library/unittest.html"\n+++\n\nTests turn expected behavior into code that can be run again. The useful unit is usually a small example of behavior with clear input, action, and assertion.\n\nPython\'s `unittest` library provides test cases, assertions, suites, and runners. Projects often use `pytest` for ergonomics, but the same idea remains: a test names behavior and fails when the behavior changes.\n\nA broad testing practice also includes fixtures, integration tests, property tests, and coverage. This example stays on the smallest standard-library loop: define behavior, assert the result, run the suite, inspect success.\n\n:::program\n```python\nimport io\nimport unittest\n\n\ndef add(left, right):\n return left + right\n\nclass AddTests(unittest.TestCase):\n def test_adds_numbers(self):\n self.assertEqual(add(2, 3), 5)\n\n def test_adds_empty_strings(self):\n self.assertEqual(add("", "py"), "py")\n\nsuite = unittest.defaultTestLoader.loadTestsFromTestCase(AddTests)\nstream = io.StringIO()\nresult = unittest.TextTestRunner(stream=stream, verbosity=0).run(suite)\nprint(result.testsRun)\nprint(result.wasSuccessful())\n```\n:::\n\n:::cell\nA test starts with behavior small enough to name. The function can be ordinary code; the test supplies a representative input and expected result.\n\n```python\ndef add(left, right):\n return left + right\n\nprint(add(2, 3))\n```\n\n```output\n5\n```\n:::\n\n:::cell\n`unittest.TestCase` groups test methods. Assertion methods such as `assertEqual` make the expected behavior explicit.\n\n```python\nimport unittest\n\nclass AddTests(unittest.TestCase):\n def test_adds_numbers(self):\n self.assertEqual(add(2, 3), 5)\n\n def test_adds_empty_strings(self):\n self.assertEqual(add("", "py"), "py")\n\nprint([name for name in dir(AddTests) if name.startswith("test_")])\n```\n\n```output\n[\'test_adds_empty_strings\', \'test_adds_numbers\']\n```\n:::\n\n:::cell\nA runner executes the suite and records whether every assertion passed. Capturing the runner stream keeps this page\'s output deterministic.\n\n```python\nimport io\n\nsuite = unittest.defaultTestLoader.loadTestsFromTestCase(AddTests)\nstream = io.StringIO()\nresult = unittest.TextTestRunner(stream=stream, verbosity=0).run(suite)\nprint(result.testsRun)\nprint(result.wasSuccessful())\n```\n\n```output\n2\nTrue\n```\n:::\n\n:::note\n- Test method names should describe behavior, not implementation details.\n- A good unit test is deterministic and independent of test order.\n- Use broader integration tests when the behavior depends on several components working together.\n:::\n', 'threads-and-processes.md': '+++\nslug = "threads-and-processes"\ntitle = "Threads and Processes"\nsection = "Standard Library"\nsummary = "Threads share memory, while processes run in separate interpreters."\ndoc_path = "/library/concurrent.futures.html"\nexpected_output = "[1, 4, 9]\\nProcessPoolExecutor\\n"\n+++\n\nThreads and processes are two ways to run work outside the current control path. Threads are useful for overlapping I/O-shaped waits, while processes are useful when CPU-bound work needs separate interpreter processes.\n\nThis is different from `asyncio`: threads and processes run callables through executors, while `async` code cooperatively awaits coroutines. Choose the smallest concurrency model that matches the bottleneck.\n\nThe executor interface lets callers submit ordinary functions without committing the rest of the code to one scheduling strategy. That makes it easier to compare thread and process boundaries at the call site.\n\n:::program\n```python\nfrom concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor\n\n\ndef square(number):\n return number * number\n\nwith ThreadPoolExecutor(max_workers=2) as pool:\n print(list(pool.map(square, [1, 2, 3])))\n\nprint(ProcessPoolExecutor.__name__)\n```\n:::\n\n:::unsupported\nDynamic Workers do not provide native threads or child processes.\n\n```python\nwith ThreadPoolExecutor(max_workers=2) as pool:\n print(list(pool.map(square, [1, 2, 3])))\n\nwith ProcessPoolExecutor(max_workers=2) as pool:\n print(list(pool.map(pow, [4, 5], [2, 2])))\n```\n:::\n\n:::cell\nA thread pool runs ordinary callables while sharing memory with the current process. `map()` returns results in input order.\n\n```python\nfrom concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor\n\n\ndef square(number):\n return number * number\n\nwith ThreadPoolExecutor(max_workers=2) as pool:\n print(list(pool.map(square, [1, 2, 3])))\n```\n\n```output\n[1, 4, 9]\n```\n:::\n\n:::cell\nA process pool uses separate Python processes. That boundary is heavier, but it can run CPU-bound work outside the current interpreter.\n\n```python\nprint(ProcessPoolExecutor.__name__)\n```\n\n```output\nProcessPoolExecutor\n```\n:::\n\n:::note\n- Threads share memory, so mutable shared state needs care.\n- Processes avoid shared interpreter state but require values to cross a process boundary.\n- Prefer `asyncio` for coroutine-based I/O and executors for ordinary blocking callables.\n:::\n', 'truth-and-size.md': '+++\nslug = "truth-and-size"\ntitle = "Truth and Size"\nsection = "Data Model"\nsummary = "__bool__ and __len__ decide how objects behave in truth tests and len()."\ndoc_path = "/reference/datamodel.html#object.__bool__"\nsee_also = [\n "truthiness",\n "special-methods",\n "container-protocols",\n]\n+++\n\nTruth tests ask an object whether it should count as true. Containers usually answer through their size, while domain objects can answer with `__bool__` when emptiness is not the right idea.\n\n`__len__` supports `len(obj)` and also provides a fallback truth value: length zero is false, non-zero length is true. `__bool__` is more direct and wins when both are present.\n\nUse these methods to match the meaning of your object. A queue can be false when it has no items; an account might be true only when it is active, regardless of its balance.\n\n:::program\n```python\nclass Inbox:\n def __init__(self, messages):\n self.messages = list(messages)\n\n def __len__(self):\n return len(self.messages)\n\nclass Account:\n def __init__(self, active):\n self.active = active\n\n def __bool__(self):\n return self.active\n\nprint(len(Inbox(["hi", "bye"])))\nprint(bool(Inbox([])))\nprint(bool(Account(False)))\n```\n:::\n\n:::cell\n`__len__` lets `len()` ask an object for its size.\n\n```python\nclass Inbox:\n def __init__(self, messages):\n self.messages = list(messages)\n\n def __len__(self):\n return len(self.messages)\n\nprint(len(Inbox(["hi", "bye"])))\n```\n\n```output\n2\n```\n:::\n\n:::cell\nIf a class has `__len__` but no `__bool__`, Python uses zero length as false.\n\n```python\nprint(bool(Inbox([])))\n```\n\n```output\nFalse\n```\n:::\n\n:::cell\n`__bool__` expresses truth directly when the answer is not just container size.\n\n```python\nclass Account:\n def __init__(self, active):\n self.active = active\n\n def __bool__(self):\n return self.active\n\nprint(bool(Account(False)))\n```\n\n```output\nFalse\n```\n:::\n\n:::note\n- Prefer `__len__` for sized containers.\n- Prefer `__bool__` when truth has domain meaning.\n- Keep truth tests unsurprising; surprising falsy objects make conditionals harder to read.\n:::\n', 'truthiness.md': '+++\nslug = "truthiness"\ntitle = "Truthiness"\nsection = "Basics"\nsummary = "Python conditions use truthiness, not only explicit booleans."\ndoc_path = "/library/stdtypes.html#truth-value-testing"\n+++\n\nTruthiness is one of Python\'s most important conveniences: conditions can test objects directly instead of requiring explicit boolean comparisons everywhere.\n\nEmpty containers, numeric zero, None, and False are false; most other values are true. This makes common checks such as if items: concise and idiomatic.\n\nUse truthiness when it reads naturally, but choose explicit comparisons when the distinction matters, such as checking whether a value is exactly None.\n\n:::program\n```python\nitems = []\nname = "Ada"\n\nif not items:\n print("no items")\n\nif name:\n print("has a name")\n\nprint(bool(0))\nprint(bool(42))\n```\n:::\n\n:::cell\nTruthiness is one of Python\'s most important conveniences: conditions can test objects directly instead of requiring explicit boolean comparisons everywhere.\n\nEmpty containers, numeric zero, None, and False are false; most other values are true. This makes common checks such as if items: concise and idiomatic.\n\n```python\nitems = []\nname = "Ada"\n\nif not items:\n print("no items")\n```\n\n```output\nno items\n```\n:::\n\n:::cell\nUse truthiness when it reads naturally, but choose explicit comparisons when the distinction matters, such as checking whether a value is exactly None.\n\n```python\nif name:\n print("has a name")\n```\n\n```output\nhas a name\n```\n:::\n\n:::cell\nUse truthiness when it reads naturally, but choose explicit comparisons when the distinction matters, such as checking whether a value is exactly None.\n\n```python\nprint(bool(0))\nprint(bool(42))\n```\n\n```output\nFalse\nTrue\n```\n:::\n\n:::note\n- Empty containers and zero-like numbers are false in conditions.\n- Use explicit comparisons when they communicate intent better than truthiness.\n:::\n', 'tuples.md': '+++\nslug = "tuples"\ntitle = "Tuples"\nsection = "Collections"\nsummary = "Tuples group a fixed number of positional values."\ndoc_path = "/tutorial/datastructures.html#tuples-and-sequences"\n+++\n\nTuples are ordered, immutable sequences. They exist for small fixed groups where position has meaning: coordinates, RGB colors, database rows, and multiple return values.\n\nUse lists for variable-length collections of similar items. Use tuples when the number of positions is part of the data shape and unpacking can give each position a useful name.\n\nBecause tuples are immutable, you cannot append or replace positions in place. If the shape needs to grow or change, a list or dataclass is usually a better fit.\n\n:::program\n```python\npoint = (3, 4)\nx, y = point\nprint(x + y)\n\nred = (255, 0, 0)\nprint(red[0])\nprint(len(red))\n\nrecord = ("Ada", 10)\nname, score = record\nprint(f"{name}: {score}")\n```\n:::\n\n:::cell\nUse a tuple for a fixed-size record where each position has a known meaning. Unpacking turns those positions into names at the point of use.\n\n```python\npoint = (3, 4)\nx, y = point\nprint(x + y)\n```\n\n```output\n7\n```\n:::\n\n:::cell\nTuples are sequences, so indexing and `len()` work. They are different from lists because their length and item references are fixed after creation.\n\n```python\nred = (255, 0, 0)\nprint(red[0])\nprint(len(red))\n```\n\n```output\n255\n3\n```\n:::\n\n:::cell\nTuples pair naturally with multiple return values and unpacking. If the fields need names everywhere, graduate to a dataclass or named tuple.\n\n```python\nrecord = ("Ada", 10)\nname, score = record\nprint(f"{name}: {score}")\n```\n\n```output\nAda: 10\n```\n:::\n\n:::note\n- Tuples are immutable sequences with fixed length.\n- Use tuples for small records where position has meaning.\n- Use lists for variable-length collections of similar items.\n:::\n', 'type-aliases.md': '+++\nslug = "type-aliases"\ntitle = "Type Aliases"\nsection = "Types"\nsummary = "Type aliases give a meaningful name to a repeated type shape."\ndoc_path = "/library/typing.html#type-aliases"\nsee_also = [\n "type-hints",\n "newtype",\n "union-and-optional-types",\n]\n+++\n\nA type alias gives a name to an annotation shape. It helps readers and type checkers understand the role of a value without repeating a long type expression everywhere.\n\nPython 3.13 supports the `type` statement for explicit aliases. Older assignment-style aliases still appear in code, but the `type` statement makes the intent clear and creates a `TypeAliasType` object at runtime.\n\nAn alias does not create a new runtime type. If you need a static distinction between compatible values such as user IDs and order IDs, use `NewType` instead.\n\n:::program\n```python\ntype UserId = int\ntype Scores = dict[UserId, int]\nLegacyName = str\n\n\ndef best_user(scores: Scores) -> UserId:\n return max(scores, key=scores.get)\n\nscores: Scores = {1: 98, 2: 91}\nprint(best_user(scores))\nprint(UserId.__name__)\nprint(LegacyName("Ada"))\n```\n:::\n\n:::cell\nThe `type` statement names an annotation shape. Here `Scores` means a dictionary from user IDs to integer scores.\n\n```python\ntype UserId = int\ntype Scores = dict[UserId, int]\n\n\ndef best_user(scores: Scores) -> UserId:\n return max(scores, key=scores.get)\n\nscores: Scores = {1: 98, 2: 91}\nprint(best_user(scores))\n```\n\n```output\n1\n```\n:::\n\n:::cell\nModern aliases are runtime objects that keep their alias name for introspection.\n\n```python\nprint(UserId.__name__)\nprint(Scores.__name__)\n```\n\n```output\nUserId\nScores\n```\n:::\n\n:::cell\nAssignment-style aliases are still common, but they are just ordinary names bound to existing objects.\n\n```python\nLegacyName = str\nprint(LegacyName("Ada"))\nprint(LegacyName is str)\n```\n\n```output\nAda\nTrue\n```\n:::\n\n:::note\n- Use aliases to name repeated or domain-specific annotation shapes.\n- A type alias does not validate values at runtime.\n- Use `NewType` when two values share a runtime representation but should not be mixed statically.\n:::\n', 'type-hints.md': '+++\nslug = "type-hints"\ntitle = "Type Hints"\nsection = "Types"\nsummary = "Annotations document expected types and power static analysis."\ndoc_path = "/library/typing.html"\n+++\n\nType hints are annotations that document expected shapes for values, parameters, and return results. They exist so tools and readers can understand API boundaries before the program runs.\n\nPython stores many annotations but does not enforce most of them at runtime. Use type hints for communication and static analysis; use validation or exceptions when runtime checks are required.\n\nThe alternative to an annotation is prose, tests, or runtime validation. Good Python code often uses all three at important boundaries.\n\n:::program\n```python\ndef total(numbers: list[int]) -> int:\n return sum(numbers)\n\nprint(total([1, 2, 3]))\nprint(total.__annotations__)\n\n\ndef label(score: int) -> str:\n return f"score={score}"\n\nprint(label("high"))\n```\n:::\n\n:::cell\nType hints document expected parameter and return shapes. Python still runs the function normally at runtime.\n\n```python\ndef total(numbers: list[int]) -> int:\n return sum(numbers)\n\nprint(total([1, 2, 3]))\n```\n\n```output\n6\n```\n:::\n\n:::cell\nPython stores annotations on the function object for tools and introspection. Type checkers use this information without changing the function call syntax.\n\n```python\nprint(total.__annotations__)\n```\n\n```output\n{\'numbers\': list[int], \'return\': }\n```\n:::\n\n:::cell\nMost hints are not runtime validation. This call passes a string where the hint says `int`; Python still calls the function because the body can format any value.\n\n```python\ndef label(score: int) -> str:\n return f"score={score}"\n\nprint(label("high"))\n```\n\n```output\nscore=high\n```\n:::\n\n:::note\n- Python does not enforce most type hints at runtime.\n- Tools like type checkers and editors use annotations to catch mistakes earlier.\n- Use runtime validation when untrusted input must be rejected while the program runs.\n:::\n', 'typed-dicts.md': '+++\nslug = "typed-dicts"\ntitle = "TypedDict"\nsection = "Types"\nsummary = "TypedDict describes dictionaries with known string keys."\ndoc_path = "/library/typing.html#typing.TypedDict"\nsee_also = [\n "dicts",\n "json",\n "dataclasses",\n]\n+++\n\n`TypedDict` describes dictionary records with known keys. It is useful for JSON-like data that should remain a dictionary instead of becoming a class instance.\n\nThe important boundary is static versus runtime behavior. Type checkers can know that `name` is a string and `score` is an integer, but at runtime the value is still an ordinary `dict`.\n\nUse `TypedDict` for external records and `dataclass` when your own program wants attribute access, methods, and construction behavior.\n\n:::program\n```python\nfrom typing import NotRequired, TypedDict\n\nclass User(TypedDict):\n name: str\n score: int\n nickname: NotRequired[str]\n\n\ndef describe(user: User) -> str:\n return f"{user[\'name\']}: {user[\'score\']}"\n\nrecord: User = {"name": "Ada", "score": 98}\nprint(describe(record))\nprint(isinstance(record, dict))\nprint(record.get("nickname", "none"))\n```\n:::\n\n:::cell\nUse `TypedDict` for JSON-like records that remain dictionaries.\n\n```python\nfrom typing import TypedDict\n\nclass User(TypedDict):\n name: str\n score: int\n\n\ndef describe(user: User) -> str:\n return f"{user[\'name\']}: {user[\'score\']}"\n\nrecord: User = {"name": "Ada", "score": 98}\nprint(describe(record))\n```\n\n```output\nAda: 98\n```\n:::\n\n:::cell\nAt runtime, a `TypedDict` value is still a plain dictionary.\n\n```python\nprint(isinstance(record, dict))\nprint(type(record).__name__)\n```\n\n```output\nTrue\ndict\n```\n:::\n\n:::cell\n`NotRequired` marks a key that type checkers should treat as optional. Runtime lookup still uses normal dictionary tools such as `get()`.\n\n```python\nfrom typing import NotRequired\n\nclass UserWithNickname(TypedDict):\n name: str\n score: int\n nickname: NotRequired[str]\n\nrecord: UserWithNickname = {"name": "Ada", "score": 98}\nprint(record.get("nickname", "none"))\n```\n\n```output\nnone\n```\n:::\n\n:::note\n- Use `TypedDict` for dictionary records from JSON or APIs.\n- Type checkers understand required and optional keys.\n- Runtime behavior is still ordinary dictionary behavior.\n:::\n', 'union-and-optional-types.md': '+++\nslug = "union-and-optional-types"\ntitle = "Union and Optional Types"\nsection = "Types"\nsummary = "The | operator describes values that may have more than one static type."\ndoc_path = "/library/typing.html#typing.Optional"\nsee_also = [\n "none",\n "type-hints",\n "match-statements",\n]\n+++\n\nA union type says that a value may have one of several static shapes. `int | str` means callers may pass either an integer or a string.\n\n`T | None` is the modern spelling for an optional value. The annotation documents that absence is expected, but the code still needs to handle `None` before using the non-optional behavior.\n\nUnions are useful at boundaries where input is flexible. Inside a function, narrow the value with an `is None`, `isinstance()`, or pattern check so the rest of the code has one clear shape.\n\n:::program\n```python\ndef label(value: int | str) -> str:\n return f"item-{value}"\n\n\ndef greeting(name: str | None) -> str:\n if name is None:\n return "hello guest"\n return f"hello {name.upper()}"\n\nprint(label(3))\nprint(label("A"))\nprint(greeting(None))\nprint(greeting("Ada"))\nprint(greeting.__annotations__)\n```\n:::\n\n:::cell\nUse `A | B` when a value may have either type. The function body should use operations that make sense for every member of the union.\n\n```python\ndef label(value: int | str) -> str:\n return f"item-{value}"\n\nprint(label(3))\nprint(label("A"))\n```\n\n```output\nitem-3\nitem-A\n```\n:::\n\n:::cell\n`str | None` means the function accepts either a string or explicit absence. Check for `None` before calling string methods.\n\n```python\ndef greeting(name: str | None) -> str:\n if name is None:\n return "hello guest"\n return f"hello {name.upper()}"\n\nprint(greeting(None))\nprint(greeting("Ada"))\n```\n\n```output\nhello guest\nhello ADA\n```\n:::\n\n:::cell\nUnion annotations are visible at runtime, but Python does not enforce them when the function is called.\n\n```python\nprint(greeting.__annotations__)\n```\n\n```output\n{\'name\': str | None, \'return\': }\n```\n:::\n\n:::note\n- Use `A | B` when a value may have either type.\n- `T | None` means absence is an expected case, not an error by itself.\n- Narrow unions before using behavior that belongs to only one member type.\n:::\n', 'unpacking.md': '+++\nslug = "unpacking"\ntitle = "Unpacking"\nsection = "Collections"\nsummary = "Unpacking binds names from sequences and mappings concisely."\ndoc_path = "/tutorial/datastructures.html#tuples-and-sequences"\n+++\n\nUnpacking binds multiple names from one iterable or mapping. It makes the structure of data visible at the point where values are introduced.\n\nStarred unpacking handles variable-length sequences by collecting the middle or remaining values. This keeps common head-tail patterns readable.\n\nDictionary unpacking with ** connects structured data to function calls. It is widely used in configuration, adapters, and code that bridges APIs.\n\n:::program\n```python\npoint = (3, 4)\nx, y = point\nprint(x, y)\n\nfirst, *middle, last = [1, 2, 3, 4]\nprint(first, middle, last)\n\ndef describe(name, language):\n print(name, language)\n\ndata = {"name": "Ada", "language": "Python"}\ndescribe(**data)\n```\n:::\n\n:::cell\nUnpacking binds multiple names from one iterable or mapping. It makes the structure of data visible at the point where values are introduced.\n\n```python\npoint = (3, 4)\nx, y = point\nprint(x, y)\n```\n\n```output\n3 4\n```\n:::\n\n:::cell\nStarred unpacking handles variable-length sequences by collecting the middle or remaining values. This keeps common head-tail patterns readable.\n\n```python\nfirst, *middle, last = [1, 2, 3, 4]\nprint(first, middle, last)\n```\n\n```output\n1 [2, 3] 4\n```\n:::\n\n:::cell\nDictionary unpacking with ** connects structured data to function calls. It is widely used in configuration, adapters, and code that bridges APIs.\n\nDictionary unpacking with ** connects structured data to function calls. It is widely used in configuration, adapters, and code that bridges APIs.\n\n```python\ndef describe(name, language):\n print(name, language)\n\ndata = {"name": "Ada", "language": "Python"}\ndescribe(**data)\n```\n\n```output\nAda Python\n```\n:::\n\n:::note\n- Starred unpacking collects the remaining values into a list.\n- Dictionary unpacking with ** is common when calling functions with structured data.\n- Prefer indexing when you need one position; prefer unpacking when naming several positions makes the shape clearer.\n:::\n', 'values.md': '+++\nslug = "values"\ntitle = "Values"\nsection = "Basics"\nsummary = "Python programs evaluate expressions into objects such as text, numbers, booleans, and None."\ndoc_path = "/library/stdtypes.html"\n+++\n\nA Python program works by evaluating expressions into values. Values are objects: text, integers, floats, booleans, `None`, and many richer types introduced later.\n\nNames point to values; they are not declarations that permanently fix a type. Operations usually produce new values, which you can print, store, compare, or pass to functions.\n\nThis page is a map, not the whole territory. Later pages explain the boundaries: equality vs identity, mutable vs immutable values, truthiness vs literal booleans, and `None` vs a missing key or an exception.\n\n:::program\n```python\ntext = "python"\ncount = 3\nratio = 2.5\nready = True\nmissing = None\n\nprint(type(text).__name__)\nprint(text.upper())\nprint(count + 4)\nprint(ratio * 2)\n\nprint(ready and count > 0)\nprint(missing is None)\n```\n:::\n\n:::cell\nStart with several built-in values. Python does not require declarations before binding these names, and each value is still an object with a type.\n\n```python\ntext = "python"\ncount = 3\nratio = 2.5\nready = True\nmissing = None\n\nprint(type(text).__name__)\n```\n\n```output\nstr\n```\n:::\n\n:::cell\nMethods and operators evaluate to new values. The original `text`, `count`, and `ratio` bindings remain ordinary objects you can reuse.\n\n```python\nprint(text.upper())\nprint(count + 4)\nprint(ratio * 2)\n```\n\n```output\nPYTHON\n7\n5.0\n```\n:::\n\n:::cell\nBoolean expressions combine facts, and `None` is checked by identity because it is a singleton absence marker.\n\n```python\nprint(ready and count > 0)\nprint(missing is None)\n```\n\n```output\nTrue\nTrue\n```\n:::\n\n:::note\n- Values are objects; names point to them and operations usually create new values.\n- Use `is None` for the absence marker, not `== None`.\n- This overview introduces boundaries that later pages explain in detail.\n:::\n', 'variables.md': '+++\nslug = "variables"\ntitle = "Variables"\nsection = "Basics"\nsummary = "Names are bound to values with assignment."\ndoc_path = "/reference/simple_stmts.html#assignment-statements"\n+++\n\nPython variables are names bound to objects. Assignment creates or rebinds a name; it does not require a declaration and it does not permanently attach a type to the name.\n\nRebinding changes which object a name refers to. Augmented assignment such as `+=` is the idiomatic way to update counters and accumulators.\n\nUse clear names for values that matter later. Python\'s flexibility makes naming more important, not less.\n\nUse assignment when a value needs a name for reuse or explanation. Prefer a direct expression when naming the intermediate value would add noise.\n\n:::program\n```python\nmessage = "hi"\nprint(message)\n\nmessage = "hello"\nprint(message)\n\ncount = 3\ncount += 1\nprint(count)\n```\n:::\n\n:::cell\nAssignment binds a name to a value. Once bound, the name can be used anywhere that value is needed.\n\n```python\nmessage = "hi"\nprint(message)\n```\n\n```output\nhi\n```\n:::\n\n:::cell\nAssignment can rebind the same name to a different value. The name is not permanently attached to the first object.\n\n```python\nmessage = "hello"\nprint(message)\n```\n\n```output\nhello\n```\n:::\n\n:::cell\nAugmented assignment reads the current binding, computes an updated value, and stores the result back under the same name.\n\n```python\ncount = 3\ncount += 1\nprint(count)\n```\n\n```output\n4\n```\n:::\n\n:::note\n- Python variables are names bound to objects, not boxes with fixed types.\n- Rebinding a name is normal.\n- Use augmented assignment for counters and accumulators.\n:::\n', 'virtual-environments.md': '+++\nslug = "virtual-environments"\ntitle = "Virtual Environments"\nsection = "Modules"\nsummary = "Virtual environments isolate a project\'s Python packages."\ndoc_path = "/library/venv.html"\nexpected_output = ".venv\\nTrue\\n"\n+++\n\nVirtual environments isolate a project\'s Python packages. They exist so one project can install dependencies without changing another project\'s environment.\n\nThe usual command-line workflow is `python -m venv .venv`, but Python also exposes the same feature through the `venv` module. This example creates a temporary environment so the example cleans up after itself.\n\nA virtual environment changes where Python looks for installed packages. It does not change the language, and it is separate from package layout, imports, and module names.\n\n:::program\n```python\nimport pathlib\nimport tempfile\nimport venv\n\nwith tempfile.TemporaryDirectory() as directory:\n env_path = pathlib.Path(directory) / ".venv"\n builder = venv.EnvBuilder(with_pip=False)\n builder.create(env_path)\n\n print(env_path.name)\n print((env_path / "pyvenv.cfg").exists())\n```\n:::\n\n:::unsupported\nDynamic Workers do not provide the `venv` module or a project environment workflow.\n\n```python\nbuilder = venv.EnvBuilder(with_pip=False)\nbuilder.create(".venv")\n```\n:::\n\n:::cell\n`venv.EnvBuilder` creates the same kind of isolated environment as `python -m venv`. A temporary directory keeps the example from leaving project files behind.\n\n```python\nimport pathlib\nimport tempfile\nimport venv\n\nwith tempfile.TemporaryDirectory() as directory:\n env_path = pathlib.Path(directory) / ".venv"\n builder = venv.EnvBuilder(with_pip=False)\n builder.create(env_path)\n\n print(env_path.name)\n print((env_path / "pyvenv.cfg").exists())\n```\n\n```output\n.venv\nTrue\n```\n:::\n\n:::note\n- A virtual environment gives a project its own install location.\n- Inside a venv, `sys.prefix` usually differs from `sys.base_prefix`.\n- Use `python -m venv .venv` at the command line for everyday project setup.\n:::\n', 'warnings.md': '+++\nslug = "warnings"\ntitle = "Warnings"\nsection = "Errors"\nsummary = "warnings report soft problems without immediately stopping the program."\ndoc_path = "/library/warnings.html"\n+++\n\nwarnings report soft problems without immediately stopping the program. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.\n\nUse it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.\n\nThe example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.\n\n:::program\n```python\nimport warnings\n\n\ndef old_name():\n warnings.warn("old_name is deprecated", DeprecationWarning, stacklevel=2)\n return "result"\n\nwarnings.simplefilter("always", DeprecationWarning)\nwith warnings.catch_warnings(record=True) as caught:\n print(old_name())\n print(caught[0].category.__name__)\n print(str(caught[0].message))\n```\n:::\n\n:::cell\nWarnings are useful for deprecations and soft failures.\n\n```python\nimport warnings\n\n\ndef old_name():\n warnings.warn("old_name is deprecated", DeprecationWarning, stacklevel=2)\n return "result"\n\nwarnings.simplefilter("always", DeprecationWarning)\nwith warnings.catch_warnings(record=True) as caught:\n print(old_name())\n print(caught[0].category.__name__)\n print(str(caught[0].message))\n```\n\n```output\nresult\nDeprecationWarning\nold_name is deprecated\n```\n:::\n\n:::note\n- Warnings are useful for deprecations and soft failures.\n- Filters decide whether warnings are ignored, shown, or turned into errors.\n- Tests often capture warnings to assert migration behavior.\n:::\n', 'while-loops.md': '+++\nslug = "while-loops"\ntitle = "While Loops"\nsection = "Control Flow"\nsummary = "while repeats until changing state makes a condition false."\ndoc_path = "/reference/compound_stmts.html#while"\n+++\n\nA `while` loop repeats while a condition remains true. Unlike `for`, which consumes an existing iterable, `while` is for state-driven repetition where the next step depends on what happened so far.\n\nThe loop body must make progress toward stopping. That progress might be decrementing a counter, reading until a sentinel value, or waiting until some external state changes.\n\nReach for `for` when you already have values to consume. Reach for `while` when the loop\'s own state decides whether another iteration is needed.\n\n:::program\n```python\nremaining = 3\nwhile remaining > 0:\n print(f"launch in {remaining}")\n remaining -= 1\nprint("liftoff")\n\nresponses = iter(["retry", "retry", "ok"])\nstatus = next(responses)\nwhile status != "ok":\n print(f"status: {status}")\n status = next(responses)\nprint(f"status: {status}")\n```\n:::\n\n:::cell\nUse `while` when the condition, not an iterable, controls repetition. Here the loop owns the countdown state and updates it each time through the body.\n\n```python\nremaining = 3\nwhile remaining > 0:\n print(f"launch in {remaining}")\n remaining -= 1\nprint("liftoff")\n```\n\n```output\nlaunch in 3\nlaunch in 2\nlaunch in 1\nliftoff\n```\n:::\n\n:::cell\nA sentinel loop stops when a special value appears. The loop does not know in advance how many retries it will need; it keeps going until the state says to stop.\n\n```python\nresponses = iter(["retry", "retry", "ok"])\nstatus = next(responses)\nwhile status != "ok":\n print(f"status: {status}")\n status = next(responses)\nprint(f"status: {status}")\n```\n\n```output\nstatus: retry\nstatus: retry\nstatus: ok\n```\n:::\n\n:::note\n- Use `while` when changing state decides whether the loop continues.\n- Update loop state inside the body so the condition can become false.\n- Prefer `for` when you already have a collection, range, iterator, or generator to consume.\n:::\n', 'yield-from.md': '+++\nslug = "yield-from"\ntitle = "Yield From"\nsection = "Iteration"\nsummary = "yield from delegates part of a generator to another iterable."\ndoc_path = "/reference/expressions.html#yield-expressions"\nsee_also = [\n "generators",\n "generator-expressions",\n "itertools",\n]\n+++\n\n`yield from` lets one generator yield every value from another iterable. It is a compact way to delegate part of a stream.\n\nUse it when a generator is mostly stitching together other iterables or sub-generators. It keeps the producer pipeline visible without writing a nested `for` loop.\n\nThe consumer still sees one stream of values.\n\n:::program\n```python\ndef page():\n yield "header"\n yield from ["intro", "body"]\n yield "footer"\n\nprint(list(page()))\n\n\ndef flatten(rows):\n for row in rows:\n yield from row\n\nprint(list(flatten([[1, 2], [3]])))\n```\n:::\n\n:::cell\n`yield from` delegates to another iterable. The caller receives one stream even though part of it came from a list.\n\n```python\ndef page():\n yield "header"\n yield from ["intro", "body"]\n yield "footer"\n\nprint(list(page()))\n```\n\n```output\n[\'header\', \'intro\', \'body\', \'footer\']\n```\n:::\n\n:::cell\nDelegation is useful when flattening nested iterables. `yield from row` replaces an inner loop that would yield each item by hand.\n\n```python\ndef flatten(rows):\n for row in rows:\n yield from row\n\nprint(list(flatten([[1, 2], [3]])))\n```\n\n```output\n[1, 2, 3]\n```\n:::\n\n:::note\n- `yield from iterable` yields each value from that iterable.\n- It keeps generator pipelines compact.\n- Use a plain `yield` when producing one value directly.\n:::\n'}
+EXAMPLE_SOURCE_FILES = {'manifest.toml': 'python_version = "3.13"\ndocs_base_url = "https://docs.python.org/3.13"\n\norder = [\n "hello-world",\n "values",\n "literals",\n "numbers",\n "booleans",\n "operators",\n "none",\n "variables",\n "constants",\n "truthiness",\n "equality-and-identity",\n "mutability",\n "object-lifecycle",\n "strings",\n "bytes-and-bytearray",\n "string-formatting",\n "conditionals",\n "guard-clauses",\n "assignment-expressions",\n "for-loops",\n "break-and-continue",\n "loop-else",\n "iterating-over-iterables",\n "iterators",\n "iterator-vs-iterable",\n "sentinel-iteration",\n "match-statements",\n "advanced-match-patterns",\n "while-loops",\n "lists",\n "tuples",\n "unpacking",\n "dicts",\n "sets",\n "slices",\n "comprehensions",\n "comprehension-patterns",\n "sorting",\n "collections-module",\n "copying-collections",\n "functions",\n "keyword-only-arguments",\n "positional-only-parameters",\n "args-and-kwargs",\n "multiple-return-values",\n "closures",\n "partial-functions",\n "scope-global-nonlocal",\n "recursion",\n "lambdas",\n "generators",\n "yield-from",\n "generator-expressions",\n "itertools",\n "decorators",\n "classes",\n "inheritance-and-super",\n "classmethods-and-staticmethods",\n "dataclasses",\n "properties",\n "special-methods",\n "truth-and-size",\n "container-protocols",\n "callable-objects",\n "operator-overloading",\n "attribute-access",\n "bound-and-unbound-methods",\n "descriptors",\n "metaclasses",\n "context-managers",\n "delete-statements",\n "exceptions",\n "assertions",\n "exception-chaining",\n "exception-groups",\n "warnings",\n "modules",\n "import-aliases",\n "packages",\n "virtual-environments",\n "type-hints",\n "runtime-type-checks",\n "union-and-optional-types",\n "type-aliases",\n "typed-dicts",\n "structured-data-shapes",\n "literal-and-final",\n "callable-types",\n "generics-and-typevar",\n "paramspec",\n "overloads",\n "casts-and-any",\n "newtype",\n "protocols",\n "abstract-base-classes",\n "enums",\n "regular-expressions",\n "number-parsing",\n "custom-exceptions",\n "json",\n "logging",\n "testing",\n "subprocesses",\n "threads-and-processes",\n "networking",\n "datetime",\n "csv-data",\n "async-await",\n "async-iteration-and-context",\n]\n', 'abstract-base-classes.md': '+++\nslug = "abstract-base-classes"\ntitle = "Abstract Base Classes"\nsection = "Classes"\nsummary = "ABC and abstractmethod enforce that subclasses implement required methods."\ndoc_path = "/library/abc.html"\nsee_also = [\n "protocols",\n "inheritance-and-super",\n "classes",\n]\n+++\n\n`ABC` and `@abstractmethod` describe an interface that subclasses must implement. The base class refuses to instantiate until a concrete subclass provides every abstract method, which catches "I forgot to implement this" at construction time rather than at the first method call.\n\nABCs are different from `Protocol`. An ABC is nominal: a class participates in the contract by inheriting from it. A `Protocol` is structural: any class with the right methods qualifies, no inheritance required. Reach for an ABC when you want shared implementation in the base class or you want `isinstance()` to mean "explicitly opted in"; reach for a `Protocol` when you only care about behavior at the API boundary.\n\nThe cost is a small amount of ceremony at the type level. The benefit is that a half-implemented subclass cannot be created by accident.\n\n:::program\n```python\nfrom abc import ABC, abstractmethod\nfrom typing import Protocol\n\nclass Shape(ABC):\n @abstractmethod\n def area(self) -> float:\n ...\n\n def describe(self) -> str:\n return f"shape with area {self.area()}"\n\ntry:\n Shape()\nexcept TypeError as error:\n print(error)\n\nclass Square(Shape):\n def __init__(self, side):\n self.side = side\n\n def area(self):\n return self.side ** 2\n\nprint(Square(3).area())\nprint(Square(3).describe())\n\nclass Incomplete(Shape):\n pass\n\ntry:\n Incomplete()\nexcept TypeError as error:\n print(error)\n\nclass HasArea(Protocol):\n def area(self) -> float:\n ...\n\nclass Triangle:\n def __init__(self, base, height):\n self.base = base\n self.height = height\n\n def area(self):\n return 0.5 * self.base * self.height\n\ndef total_area(shapes: list[HasArea]) -> float:\n return sum(shape.area() for shape in shapes)\n\nprint(total_area([Square(3), Triangle(4, 3)]))\nprint(isinstance(Triangle(4, 3), Shape))\nprint(isinstance(Square(3), Shape))\n```\n:::\n\n:::cell\n`ABC` plus `@abstractmethod` declares the contract. Trying to construct the base class itself fails because at least one method has no implementation. A concrete `describe()` lives alongside the abstract `area()` so subclasses inherit shared behavior for free.\n\n```python\nfrom abc import ABC, abstractmethod\n\nclass Shape(ABC):\n @abstractmethod\n def area(self) -> float:\n ...\n\n def describe(self) -> str:\n return f"shape with area {self.area()}"\n\ntry:\n Shape()\nexcept TypeError as error:\n print(error)\n```\n\n```output\nCan\'t instantiate abstract class Shape without an implementation for abstract method \'area\'\n```\n:::\n\n:::cell\nA subclass that implements every abstract method is concrete and can be instantiated. It also inherits the non-abstract methods from the base class.\n\n```python\nclass Square(Shape):\n def __init__(self, side):\n self.side = side\n\n def area(self):\n return self.side ** 2\n\nprint(Square(3).area())\nprint(Square(3).describe())\n```\n\n```output\n9\nshape with area 9\n```\n:::\n\n:::cell\nA subclass that forgets to implement an abstract method also cannot be instantiated — that is the value the ABC adds. The error fires at construction, not when something later tries to call the missing method.\n\n```python\nclass Incomplete(Shape):\n pass\n\ntry:\n Incomplete()\nexcept TypeError as error:\n print(error)\n```\n\n```output\nCan\'t instantiate abstract class Incomplete without an implementation for abstract method \'area\'\n```\n:::\n\n:::cell\nContrast with `Protocol`. A `HasArea` protocol accepts any class with an `area()` method, no inheritance required. `Triangle` does not inherit from `Shape`, so it satisfies the protocol but fails `isinstance(_, Shape)`. `Square` satisfies both because it explicitly inherited from the ABC.\n\n```python\nfrom typing import Protocol\n\nclass HasArea(Protocol):\n def area(self) -> float:\n ...\n\nclass Triangle:\n def __init__(self, base, height):\n self.base = base\n self.height = height\n\n def area(self):\n return 0.5 * self.base * self.height\n\ndef total_area(shapes: list[HasArea]) -> float:\n return sum(shape.area() for shape in shapes)\n\nprint(total_area([Square(3), Triangle(4, 3)]))\nprint(isinstance(Triangle(4, 3), Shape))\nprint(isinstance(Square(3), Shape))\n```\n\n```output\n15.0\nFalse\nTrue\n```\n:::\n\n:::note\n- `ABC` plus `@abstractmethod` blocks instantiation until every abstract method has an implementation.\n- ABCs are nominal — subclasses opt in by inheriting; `isinstance()` reflects that opt-in.\n- Protocols are structural — any class with the right shape qualifies, regardless of inheritance.\n- Prefer an ABC when shared implementation or explicit opt-in matters; prefer a Protocol when only behavior at the API boundary matters.\n:::\n', 'advanced-match-patterns.md': '+++\nslug = "advanced-match-patterns"\ntitle = "Advanced Match Patterns"\nsection = "Control Flow"\nsummary = "match patterns can destructure sequences, combine alternatives, and add guards."\ndoc_path = "/tutorial/controlflow.html#match-statements"\nsee_also = [\n "match-statements",\n "tuples",\n "classes",\n]\n+++\n\nStructural pattern matching is more than equality checks. Patterns can destructure sequences, match several alternatives, capture the rest of a sequence, and use guards.\n\nUse these forms when the shape of data is the decision. If the decision is only a single boolean condition, ordinary `if` statements are usually clearer.\n\nThe wildcard `_` catches everything not matched earlier.\n\n:::program\n```python\ndef describe(command):\n match command:\n case ["move", x, y] if x >= 0 and y >= 0:\n return f"move to {x},{y}"\n case ["quit" | "exit"]:\n return "stop"\n case ["echo", *words]:\n return " ".join(words)\n case _:\n return "unknown"\n\nprint(describe(["move", 2, 3]))\nprint(describe(["exit"]))\nprint(describe(["echo", "hello", "python"]))\nprint(describe(["move", -1, 3]))\n```\n:::\n\n:::cell\nSequence patterns match by position. A guard after `if` adds a condition that must also be true.\n\n```python\ndef describe(command):\n match command:\n case ["move", x, y] if x >= 0 and y >= 0:\n return f"move to {x},{y}"\n case ["quit" | "exit"]:\n return "stop"\n case ["echo", *words]:\n return " ".join(words)\n case _:\n return "unknown"\n\nprint(describe(["move", 2, 3]))\n```\n\n```output\nmove to 2,3\n```\n:::\n\n:::cell\nAn OR pattern accepts several alternatives in one case. A star pattern captures the rest of a sequence.\n\n```python\nprint(describe(["exit"]))\nprint(describe(["echo", "hello", "python"]))\n```\n\n```output\nstop\nhello python\n```\n:::\n\n:::cell\nThe wildcard `_` catches values that did not match earlier cases. Here the guard rejects the negative coordinate.\n\n```python\nprint(describe(["move", -1, 3]))\n```\n\n```output\nunknown\n```\n:::\n\n:::note\n- Use `case _` as a wildcard fallback.\n- Guards refine a pattern after the structure matches.\n- OR patterns and star patterns keep shape-based branches compact.\n:::\n', 'args-and-kwargs.md': '+++\nslug = "args-and-kwargs"\ntitle = "Args and Kwargs"\nsection = "Functions"\nsummary = "*args collects extra positional arguments and **kwargs collects named ones."\ndoc_path = "/tutorial/controlflow.html#arbitrary-argument-lists"\n+++\n\n`*args` and `**kwargs` let a function accept flexible positional and keyword arguments. They are the function-definition counterpart to unpacking at a call site.\n\nThese parameters are useful for wrappers, decorators, logging helpers, and APIs that forward arguments to another function.\n\nThey should not replace clear signatures. If a function has a stable interface, explicit parameters document expectations better than a bag of arguments.\n\n:::program\n```python\ndef total(*numbers):\n return sum(numbers)\n\nprint(total(2, 3, 5))\n\n\ndef describe(**metadata):\n print(metadata)\n\ndescribe(owner="Ada", public=True)\n\n\ndef report(title, *items, **metadata):\n print(title)\n print(items)\n print(metadata)\n\nreport("scores", 10, 9, owner="Ada")\n```\n:::\n\n:::cell\n`*args` collects extra positional arguments into a tuple. This fits functions that naturally accept any number of similar values.\n\n```python\ndef total(*numbers):\n return sum(numbers)\n\nprint(total(2, 3, 5))\n```\n\n```output\n10\n```\n:::\n\n:::cell\n`**kwargs` collects named arguments into a dictionary. The names become string keys.\n\n```python\ndef describe(**metadata):\n print(metadata)\n\ndescribe(owner="Ada", public=True)\n```\n\n```output\n{\'owner\': \'Ada\', \'public\': True}\n```\n:::\n\n:::cell\nA function can combine explicit parameters, `*args`, and `**kwargs`. Put the flexible parts last so the fixed shape remains visible.\n\n```python\ndef report(title, *items, **metadata):\n print(title)\n print(items)\n print(metadata)\n\nreport("scores", 10, 9, owner="Ada")\n```\n\n```output\nscores\n(10, 9)\n{\'owner\': \'Ada\'}\n```\n:::\n\n:::note\n- Use these tools when a function naturally accepts a flexible shape.\n- Prefer explicit parameters when the accepted arguments are known and fixed.\n- `*args` is a tuple; `**kwargs` is a dictionary.\n:::\n', 'assertions.md': '+++\nslug = "assertions"\ntitle = "Assertions"\nsection = "Errors"\nsummary = "assert documents internal assumptions and fails loudly when they are false."\ndoc_path = "/reference/simple_stmts.html#the-assert-statement"\nsee_also = [\n "exceptions",\n "custom-exceptions",\n "type-hints",\n]\n+++\n\n`assert` checks an internal assumption. If the condition is false, Python raises `AssertionError` with an optional message.\n\nUse assertions for programmer assumptions, not for validating user input or external data. Input validation should raise ordinary exceptions that production code expects to handle.\n\nAssertions make invariants executable while keeping the successful path compact.\n\n:::program\n```python\ndef average(scores):\n assert scores, "scores must not be empty"\n return sum(scores) / len(scores)\n\nprint(average([8, 10]))\n\ntry:\n average([])\nexcept AssertionError as error:\n print(error)\n```\n:::\n\n:::cell\nWhen the assertion is true, execution continues normally. The assertion documents the function\'s internal expectation.\n\n```python\ndef average(scores):\n assert scores, "scores must not be empty"\n return sum(scores) / len(scores)\n\nprint(average([8, 10]))\n```\n\n```output\n9.0\n```\n:::\n\n:::cell\nWhen the assertion is false, Python raises `AssertionError`. This signals a broken assumption, not a normal recovery path.\n\n```python\ntry:\n average([])\nexcept AssertionError as error:\n print(error)\n```\n\n```output\nscores must not be empty\n```\n:::\n\n:::note\n- Use `assert` for internal invariants and debugging assumptions.\n- Use explicit exceptions for user input, files, network responses, and other expected failures.\n- Assertions can be disabled with Python optimization flags, so do not rely on them for security checks.\n:::\n', 'assignment-expressions.md': '+++\nslug = "assignment-expressions"\ntitle = "Assignment Expressions"\nsection = "Control Flow"\nsummary = "The walrus operator assigns a value inside an expression."\ndoc_path = "/reference/expressions.html#assignment-expressions"\nsee_also = [\n "conditionals",\n "while-loops",\n "variables",\n]\n+++\n\nThe assignment expression operator `:=` assigns a name while evaluating an expression. It is often called the walrus operator.\n\nUse it when computing a value and testing it are naturally one step. Avoid it when a separate assignment would make the code easier to read.\n\nThe boundary is readability: the walrus operator can remove duplication, but it should not hide important state changes.\n\n:::program\n```python\nmessages = ["hello", "", "python"]\n\nfor message in messages:\n if length := len(message):\n print(message, length)\n\nqueue = ["retry", "ok"]\nwhile (status := queue.pop(0)) != "ok":\n print(status)\nprint(status)\n```\n:::\n\n:::cell\nAn assignment expression can name a computed value while a condition tests it. Here empty strings are skipped because their length is zero.\n\n```python\nmessages = ["hello", "", "python"]\n\nfor message in messages:\n if length := len(message):\n print(message, length)\n```\n\n```output\nhello 5\npython 6\n```\n:::\n\n:::cell\nThe same idea works in loops that read state until a sentinel appears. The assignment and comparison stay together.\n\n```python\nqueue = ["retry", "ok"]\nwhile (status := queue.pop(0)) != "ok":\n print(status)\nprint(status)\n```\n\n```output\nretry\nok\n```\n:::\n\n:::note\n- `name := expression` assigns and evaluates to the assigned value.\n- Use it to avoid computing the same value twice.\n- Prefer a normal assignment when the expression becomes hard to scan.\n:::\n', 'async-await.md': '+++\nslug = "async-await"\ntitle = "Async Await"\nsection = "Async"\nsummary = "async def creates coroutines, and await pauses until awaitable work completes."\ndoc_path = "/library/asyncio-task.html"\nsee_also = [\n "async-iteration-and-context",\n "functions",\n "context-managers",\n]\n+++\n\n`async def` creates a coroutine function. Calling it creates a coroutine object; the body runs when an event loop awaits or schedules it.\n\n`await` pauses the current coroutine until another awaitable completes. This lets one event loop make progress on other work while a task waits for I/O.\n\nCloudflare Workers handlers are asynchronous, so understanding `await` is practical for fetch calls, bindings, and service interactions even when a small example uses `asyncio.sleep(0)` as a stand-in.\n\nThe alternative is ordinary `def` for work that completes immediately. Use async code for I/O-shaped waiting, not as a faster replacement for CPU-bound Python.\n\n:::program\n```python\nimport asyncio\n\nasync def fetch_title(slug):\n await asyncio.sleep(0)\n return slug.replace("-", " ").title()\n\nasync def main():\n title = await fetch_title("async-await")\n print(title)\n titles = await asyncio.gather(fetch_title("json"), fetch_title("datetime"))\n print(titles)\n\nasyncio.run(main())\n\n\nclass Session:\n async def __aenter__(self):\n print("open")\n return self\n\n async def __aexit__(self, *_):\n print("close")\n return False\n\n\nasync def stream():\n for slug in ["json", "datetime"]:\n await asyncio.sleep(0)\n yield slug\n\n\nasync def driver():\n async with Session():\n async for slug in stream():\n print(slug)\n\nasyncio.run(driver())\n```\n:::\n\n:::cell\nAn `async def` function returns a coroutine object when called. The function body has not produced its final result yet.\n\n```python\nimport asyncio\n\nasync def fetch_title(slug):\n await asyncio.sleep(0)\n return slug.replace("-", " ").title()\n\ncoroutine = fetch_title("async-await")\nprint(coroutine.__class__.__name__)\ncoroutine.close()\n```\n\n```output\ncoroutine\n```\n:::\n\n:::cell\nUse `await` inside another coroutine to get the eventual result. `asyncio.run()` starts an event loop for the top-level coroutine.\n\n```python\nasync def main():\n title = await fetch_title("async-await")\n print(title)\n\nasyncio.run(main())\n```\n\n```output\nAsync Await\n```\n:::\n\n:::cell\n`asyncio.gather()` awaits several awaitables and returns their results in order. This is the shape used when independent I/O operations can progress together.\n\n```python\nasync def main():\n titles = await asyncio.gather(fetch_title("json"), fetch_title("datetime"))\n print(titles)\n\nasyncio.run(main())\n```\n\n```output\n[\'Json\', \'Datetime\']\n```\n:::\n\n:::cell\n`async with` and `async for` are the asynchronous forms of context managers and iteration. A class implements `__aenter__`/`__aexit__` to act as an async context manager; an `async def` function with `yield` becomes an async generator. The dedicated [async iteration and context](/iteration/async-iteration-and-context) page explains the protocols in depth.\n\n```python\nclass Session:\n async def __aenter__(self):\n print("open")\n return self\n\n async def __aexit__(self, *_):\n print("close")\n return False\n\n\nasync def stream():\n for slug in ["json", "datetime"]:\n await asyncio.sleep(0)\n yield slug\n\n\nasync def driver():\n async with Session():\n async for slug in stream():\n print(slug)\n\nasyncio.run(driver())\n```\n\n```output\nopen\njson\ndatetime\nclose\n```\n:::\n\n:::note\n- Calling an async function creates a coroutine object.\n- `await` yields control until an awaitable completes.\n- Workers request handlers are async, so this pattern appears around fetches and bindings.\n- Prefer ordinary functions when there is no awaitable work to coordinate.\n:::\n', 'async-iteration-and-context.md': '+++\nslug = "async-iteration-and-context"\ntitle = "Async Iteration and Context"\nsection = "Async"\nsummary = "async for and async with consume asynchronous streams and cleanup protocols."\ndoc_path = "/reference/compound_stmts.html#async-for"\nsee_also = [\n "async-await",\n "iterators",\n "context-managers",\n]\n+++\n\n`async for` consumes an asynchronous iterator: a stream whose next value may require `await`. `async with` surrounds a block with asynchronous setup and cleanup.\n\nThese forms appear around network streams, database cursors, locks, and service clients where both iteration and cleanup may wait on I/O.\n\nUse ordinary `for` and `with` when producing the next value or cleaning up does not need to await anything.\n\nThe syntax mirrors `for` and `with`, but the protocol methods are asynchronous.\n\n:::program\n```python\nimport asyncio\n\nasync def titles():\n for slug in ["values", "async-await"]:\n await asyncio.sleep(0)\n yield slug.replace("-", " ").title()\n\nclass Session:\n async def __aenter__(self):\n print("open")\n return self\n\n async def __aexit__(self, exc_type, exc, tb):\n print("close")\n\nasync def main():\n async with Session():\n async for title in titles():\n print(title)\n\nasyncio.run(main())\n```\n:::\n\n:::cell\nAn async generator can `await` before yielding each value. `async for` consumes those values with the asynchronous iteration protocol.\n\n```python\nimport asyncio\n\nasync def titles():\n for slug in ["values", "async-await"]:\n await asyncio.sleep(0)\n yield slug.replace("-", " ").title()\n\nprint(titles.__name__)\n```\n\n```output\ntitles\n```\n:::\n\n:::cell\nAn async context manager defines `__aenter__` and `__aexit__`. `async with` awaits setup and cleanup around the block.\n\n```python\nclass Session:\n async def __aenter__(self):\n print("open")\n return self\n\n async def __aexit__(self, exc_type, exc, tb):\n print("close")\n\nprint(Session.__name__)\n```\n\n```output\nSession\n```\n:::\n\n:::cell\nThe top-level coroutine combines both protocols: open the async resource, then consume the async stream inside it.\n\n```python\nasync def main():\n async with Session():\n async for title in titles():\n print(title)\n\nasyncio.run(main())\n```\n\n```output\nopen\nValues\nAsync Await\nclose\n```\n:::\n\n:::note\n- `async for` consumes asynchronous iterators.\n- `async with` awaits asynchronous setup and cleanup.\n- These forms are common around I/O-shaped resources.\n:::\n', 'attribute-access.md': '+++\nslug = "attribute-access"\ntitle = "Attribute Access"\nsection = "Data Model"\nsummary = "Attribute hooks customize lookup, missing attributes, and assignment."\ndoc_path = "/reference/datamodel.html#customizing-attribute-access"\nsee_also = [\n "properties",\n "descriptors",\n "special-methods",\n "bound-and-unbound-methods",\n]\n+++\n\nAttribute access is usually simple: `obj.name` looks up an attribute. Python exposes hooks for the uncommon cases where lookup or assignment needs to be customized.\n\n`__getattr__` runs only when normal lookup fails, which makes it a safer hook for computed fallback attributes. `__setattr__` runs for every assignment, so it should be used sparingly and carefully.\n\nPrefer ordinary attributes and `@property` first. Reach for these hooks when an object is intentionally adapting another interface, validating all assignments, or exposing dynamic fields.\n\n:::program\n```python\nclass Settings:\n def __init__(self, values):\n self._values = dict(values)\n\n def __getattr__(self, name):\n try:\n return self._values[name]\n except KeyError as error:\n raise AttributeError(name) from error\n\n def __setattr__(self, name, value):\n if name.startswith("_"):\n object.__setattr__(self, name, value)\n else:\n self._values[name] = value\n\nsettings = Settings({"theme": "dark"})\nprint(settings.theme)\nsettings.volume = 7\nprint(settings._values["volume"])\n```\n:::\n\n:::cell\nNormal initialization still needs to set real attributes. Calling `object.__setattr__` avoids recursing through your own hook.\n\n```python\nclass Settings:\n def __init__(self, values):\n self._values = dict(values)\n\nsettings = Settings({"theme": "dark"})\nprint(settings._values)\n```\n\n```output\n{\'theme\': \'dark\'}\n```\n:::\n\n:::cell\n`__getattr__` runs only for missing attributes, so it can provide fallback lookup.\n\n```python\nclass Settings:\n def __init__(self, values):\n self._values = dict(values)\n\n def __getattr__(self, name):\n try:\n return self._values[name]\n except KeyError as error:\n raise AttributeError(name) from error\n\nsettings = Settings({"theme": "dark"})\nprint(settings.theme)\n```\n\n```output\ndark\n```\n:::\n\n:::cell\n`__setattr__` intercepts assignment. This example stores public names in the backing dictionary.\n\n```python\nclass Settings:\n def __init__(self, values):\n self._values = dict(values)\n\n def __setattr__(self, name, value):\n if name.startswith("_"):\n object.__setattr__(self, name, value)\n else:\n self._values[name] = value\n\nsettings = Settings({"theme": "dark"})\nsettings.volume = 7\nprint(settings._values["volume"])\n```\n\n```output\n7\n```\n:::\n\n:::note\n- `__getattr__` is narrower than `__getattribute__` because it handles only missing attributes.\n- `__setattr__` affects every assignment on the instance.\n- Use `property` or descriptors when the behavior is attached to a known attribute name.\n:::\n', 'booleans.md': '+++\nslug = "booleans"\ntitle = "Booleans"\nsection = "Basics"\nsummary = "Booleans represent truth values and combine with logical operators."\ndoc_path = "/library/stdtypes.html#boolean-type-bool"\n+++\n\nBooleans are the values `True` and `False`. They are produced by comparisons and combined with `and`, `or`, and `not`.\n\nPython\'s logical operators short-circuit. That means the right side is evaluated only when needed, which keeps guard checks efficient and safe.\n\nBooleans are also connected to truthiness: many objects can be tested in conditions even when they are not literally `True` or `False`.\n\n:::program\n```python\nlogged_in = True\nhas_permission = False\n\nprint(logged_in and has_permission)\nprint(logged_in or has_permission)\nprint(not has_permission)\n\nname = "Ada"\nprint(name == "Ada" and len(name) > 0)\n\nprint(isinstance(True, int))\nprint(True + True)\nprint(sum([True, True, False, True]))\n\ndef is_strict_int(value):\n return isinstance(value, int) and not isinstance(value, bool)\n\nprint(is_strict_int(True))\nprint(is_strict_int(1))\n```\n:::\n\n:::cell\nUse booleans for facts that are either true or false. Python spells the constants `True` and `False`.\n\nUse `and`, `or`, and `not` to combine truth values. These operators read like English and short-circuit when possible.\n\n```python\nlogged_in = True\nhas_permission = False\n\nprint(logged_in and has_permission)\nprint(logged_in or has_permission)\nprint(not has_permission)\n```\n\n```output\nFalse\nTrue\nTrue\n```\n:::\n\n:::cell\nComparisons produce booleans too, so they compose naturally with logical operators in conditions and validation checks.\n\n```python\nname = "Ada"\nprint(name == "Ada" and len(name) > 0)\n```\n\n```output\nTrue\n```\n:::\n\n:::cell\n`bool` is a subclass of `int`, which is occasionally a footgun. `True` behaves as `1` and `False` as `0` in arithmetic, and `isinstance(True, int)` is `True`. When a function must reject booleans, exclude them explicitly with `isinstance(value, int) and not isinstance(value, bool)`.\n\n```python\nprint(isinstance(True, int))\nprint(True + True)\nprint(sum([True, True, False, True]))\n\ndef is_strict_int(value):\n return isinstance(value, int) and not isinstance(value, bool)\n\nprint(is_strict_int(True))\nprint(is_strict_int(1))\n```\n\n```output\nTrue\n2\n3\nFalse\nTrue\n```\n:::\n\n:::note\n- Boolean constants are `True` and `False`, with capital letters.\n- `and` and `or` short-circuit: Python does not evaluate the right side if the left side already determines the result.\n- Prefer truthiness for containers and explicit comparisons when the exact boolean condition matters.\n- `bool` subclasses `int`; `isinstance(True, int)` is `True`. Exclude booleans explicitly when only "real" integers should pass.\n:::\n', 'bound-and-unbound-methods.md': '+++\nslug = "bound-and-unbound-methods"\ntitle = "Bound and Unbound Methods"\nsection = "Data Model"\nsummary = "instance.method binds self automatically; Class.method is a plain function."\ndoc_path = "/reference/datamodel.html#instance-methods"\nsee_also = [\n "classes",\n "attribute-access",\n "descriptors",\n "callable-objects",\n]\n+++\n\nWhen you write `instance.method`, Python returns a bound method — a callable that already remembers which instance to pass as `self`. When you write `Class.method`, you get the underlying function back, and calling it requires passing an instance yourself.\n\nThat distinction is why methods can be stored in collections, passed as callbacks, and called later without losing track of the object they belong to. Each bound method carries its own `__self__`, so two callables produced from two different instances stay independent even when their underlying function is the same.\n\nThe mechanism is the descriptor protocol: a function attached to a class implements `__get__`, and that hook turns attribute access on an instance into a bound method. The page does not need that detail to use methods, but it explains what is happening underneath.\n\n:::program\n```python\nclass Counter:\n def __init__(self, start=0):\n self.value = start\n\n def increment(self):\n self.value += 1\n return self.value\n\nbound_counter = Counter(10)\nm = bound_counter.increment\nprint(m.__self__ is bound_counter)\nprint(m())\nprint(m())\n\nunbound_counter = Counter(0)\nunbound = Counter.increment\nprint(type(unbound).__name__)\nprint(unbound(unbound_counter))\nprint(unbound(unbound_counter))\n\nhandlers = []\nfor _ in range(2):\n handlers.append(Counter().increment)\n\nprint(handlers[0]())\nprint(handlers[0]())\nprint(handlers[1]())\n\ndescriptor_counter = Counter(0)\nfunc = Counter.__dict__["increment"]\nprint(type(func).__name__)\nrebound = func.__get__(descriptor_counter, Counter)\nprint(type(rebound).__name__)\nprint(rebound.__self__ is descriptor_counter)\n```\n:::\n\n:::cell\n`instance.method` returns a bound method. The method already remembers the instance through `__self__`, so calling it does not require passing `self` again.\n\n```python\nclass Counter:\n def __init__(self, start=0):\n self.value = start\n\n def increment(self):\n self.value += 1\n return self.value\n\nbound_counter = Counter(10)\nm = bound_counter.increment\nprint(m.__self__ is bound_counter)\nprint(m())\nprint(m())\n```\n\n```output\nTrue\n11\n12\n```\n:::\n\n:::cell\n`Class.method` returns the underlying function — there is no `self` attached. Calling it requires passing the instance as the first argument explicitly. Using a fresh counter here makes the output independent of the previous cell.\n\n```python\nunbound_counter = Counter(0)\nunbound = Counter.increment\nprint(type(unbound).__name__)\nprint(unbound(unbound_counter))\nprint(unbound(unbound_counter))\n```\n\n```output\nfunction\n1\n2\n```\n:::\n\n:::cell\nBound methods are first-class values. They can be stored in lists, passed to other functions, and called later. Each bound method carries its own `__self__`, so two methods produced from two different instances stay independent.\n\n```python\nhandlers = []\nfor _ in range(2):\n handlers.append(Counter().increment)\n\nprint(handlers[0]())\nprint(handlers[0]())\nprint(handlers[1]())\n```\n\n```output\n1\n2\n1\n```\n:::\n\n:::cell\nThe binding is the descriptor protocol at work. The function lives on the class as a plain function; instance attribute access invokes `__get__`, which returns a bound method that knows the instance.\n\n```python\ndescriptor_counter = Counter(0)\nfunc = Counter.__dict__["increment"]\nprint(type(func).__name__)\nrebound = func.__get__(descriptor_counter, Counter)\nprint(type(rebound).__name__)\nprint(rebound.__self__ is descriptor_counter)\n```\n\n```output\nfunction\nmethod\nTrue\n```\n:::\n\n:::note\n- `instance.method` produces a bound method whose `__self__` is the instance.\n- `Class.method` produces the plain function and requires you to pass the instance.\n- Each bound method is its own object; storing one captures its instance.\n- The binding is implemented by the descriptor protocol on the function object.\n:::\n', 'break-and-continue.md': '+++\nslug = "break-and-continue"\ntitle = "Break and Continue"\nsection = "Control Flow"\nsummary = "break exits a loop early, while continue skips to the next iteration."\ndoc_path = "/tutorial/controlflow.html#break-and-continue-statements"\nsee_also = [\n "for-loops",\n "while-loops",\n "loop-else",\n]\n+++\n\n`break` and `continue` control the nearest enclosing loop. They exist for loops whose body discovers an early stop rule or an item-level skip rule.\n\nUse `continue` when the current item should not run the rest of the body. Use `break` when no later item should be processed.\n\nThe alternative is ordinary `if`/`else` nesting. Prefer `break` and `continue` when they keep the normal path flatter and easier to read.\n\n:::program\n```python\nnames = ["Ada", "", "Grace"]\nfor name in names:\n if not name:\n continue\n print(name)\n\ncommands = ["load", "save", "stop", "delete"]\nfor command in commands:\n if command == "stop":\n break\n print(command)\n```\n:::\n\n:::cell\n`continue` skips the rest of the current iteration. The empty name is ignored, and the loop moves on to the next value.\n\n```python\nnames = ["Ada", "", "Grace"]\nfor name in names:\n if not name:\n continue\n print(name)\n```\n\n```output\nAda\nGrace\n```\n:::\n\n:::cell\n`break` exits the loop immediately. The value after `stop` is never processed because the loop has already ended.\n\n```python\ncommands = ["load", "save", "stop", "delete"]\nfor command in commands:\n if command == "stop":\n break\n print(command)\n```\n\n```output\nload\nsave\n```\n:::\n\n:::note\n- `continue` skips to the next loop iteration.\n- `break` exits the nearest enclosing loop immediately.\n- Prefer plain `if`/`else` when the loop does not need early skip or early stop behavior.\n:::\n', 'bytes-and-bytearray.md': '+++\nslug = "bytes-and-bytearray"\ntitle = "Bytes and Bytearray"\nsection = "Basics"\nsummary = "bytes and bytearray store binary data, not Unicode text."\ndoc_path = "/library/stdtypes.html#binary-sequence-types-bytes-bytearray-memoryview"\nsee_also = [\n "strings",\n "literals",\n "networking",\n]\n+++\n\n`str` stores Unicode text. `bytes` stores raw byte values. The boundary matters whenever text leaves Python for a file, network protocol, subprocess, or binary format.\n\nEncoding turns text into bytes with a named encoding such as UTF-8. Decoding turns bytes back into text. The lengths can differ because one Unicode character may require several bytes.\n\nUse immutable `bytes` for stable binary data and `bytearray` when the bytes must be changed in place.\n\n:::program\n```python\ntext = "café"\ndata = text.encode("utf-8")\n\nprint(data)\nprint(len(text), len(data))\nprint(data.decode("utf-8"))\nprint(data[0])\n\npacket = bytearray(b"py")\npacket[0] = ord("P")\nprint(packet)\n```\n:::\n\n:::cell\nEncode text when an external boundary needs bytes. UTF-8 uses one byte for ASCII characters and more than one byte for many other characters.\n\n```python\ntext = "café"\ndata = text.encode("utf-8")\nprint(data)\nprint(len(text), len(data))\n```\n\n```output\nb\'caf\\xc3\\xa9\'\n4 5\n```\n:::\n\n:::cell\nDecode bytes when the program needs text again. The decoder must match the encoding used at the boundary.\n\n```python\nprint(data.decode("utf-8"))\n```\n\n```output\ncafé\n```\n:::\n\n:::cell\nIndexing a `bytes` object returns an integer byte value, not a one-character `bytes` object.\n\n```python\nprint(data[0])\n```\n\n```output\n99\n```\n:::\n\n:::cell\n`bytes` is immutable. Use `bytearray` when binary data must be changed in place.\n\n```python\npacket = bytearray(b"py")\npacket[0] = ord("P")\nprint(packet)\n```\n\n```output\nbytearray(b\'Py\')\n```\n:::\n\n:::note\n- Encode text when an external boundary needs bytes.\n- Decode bytes when you want text again.\n- Indexing `bytes` returns integers from 0 to 255.\n- Use `bytearray` when binary data must be changed in place.\n:::\n', 'callable-objects.md': '+++\nslug = "callable-objects"\ntitle = "Callable Objects"\nsection = "Data Model"\nsummary = "__call__ lets an instance behave like a function while keeping state."\ndoc_path = "/reference/datamodel.html#object.__call__"\nsee_also = [\n "functions",\n "closures",\n "callable-types",\n "bound-and-unbound-methods",\n]\n+++\n\nFunctions are not the only callable objects in Python. Any instance can be called with parentheses when its class defines `__call__`.\n\nCallable objects are useful when behavior needs remembered configuration or evolving state. A closure can do this too; a class is often clearer when the state has multiple fields or needs named methods.\n\nThe tradeoff is ceremony. Use a function for simple behavior, a closure for small captured state, and a callable object when naming the state improves the interface.\n\n:::program\n```python\nclass Multiplier:\n def __init__(self, factor):\n self.factor = factor\n self.calls = 0\n\n def __call__(self, value):\n self.calls += 1\n return value * self.factor\n\ndouble = Multiplier(2)\nprint(double(5))\nprint(double(7))\nprint(double.calls)\n```\n:::\n\n:::cell\nA callable object starts as ordinary state stored on an instance.\n\n```python\nclass Multiplier:\n def __init__(self, factor):\n self.factor = factor\n self.calls = 0\n\ndouble = Multiplier(2)\nprint(double.factor)\n```\n\n```output\n2\n```\n:::\n\n:::cell\n`__call__` makes the instance usable with function-call syntax.\n\n```python\nclass Multiplier:\n def __init__(self, factor):\n self.factor = factor\n self.calls = 0\n\n def __call__(self, value):\n self.calls += 1\n return value * self.factor\n\ndouble = Multiplier(2)\nprint(double(5))\nprint(double(7))\n```\n\n```output\n10\n14\n```\n:::\n\n:::cell\nBecause the callable is still an object, it can remember state across calls.\n\n```python\nprint(double.calls)\n```\n\n```output\n2\n```\n:::\n\n:::note\n- `callable(obj)` checks whether an object can be called.\n- Callable objects are good for named, stateful behavior.\n- Prefer plain functions when no instance state is needed.\n:::\n', 'callable-types.md': '+++\nslug = "callable-types"\ntitle = "Callable Types"\nsection = "Types"\nsummary = "Callable annotations describe functions passed as values."\ndoc_path = "/library/typing.html#annotating-callable-objects"\nsee_also = [\n "functions",\n "callable-objects",\n "protocols",\n]\n+++\n\nCallable annotations describe values that can be called like functions. They are useful when a function accepts a callback, strategy, predicate, or transformation.\n\n`Callable[[int], int]` says how the callback will be called: one integer argument, integer result. The annotation helps tools and readers, while runtime still only needs an object that is actually callable.\n\nUse `Callable` for simple call shapes. Use a protocol when the callback needs named attributes, overloaded signatures, or a more descriptive interface.\n\n:::program\n```python\nfrom collections.abc import Callable\n\n\ndef apply_twice(value: int, func: Callable[[int], int]) -> int:\n return func(func(value))\n\n\ndef add_one(number: int) -> int:\n return number + 1\n\nclass Doubler:\n def __call__(self, number: int) -> int:\n return number * 2\n\nprint(apply_twice(3, add_one))\nprint(apply_twice(3, Doubler()))\nprint(callable(add_one), callable(Doubler()))\n```\n:::\n\n:::cell\nUse `Callable[[Arg], Return]` for function-shaped values. The callback is passed in and called by the receiving function.\n\n```python\nfrom collections.abc import Callable\n\n\ndef apply_twice(value: int, func: Callable[[int], int]) -> int:\n return func(func(value))\n\n\ndef add_one(number: int) -> int:\n return number + 1\n\nprint(apply_twice(3, add_one))\n```\n\n```output\n5\n```\n:::\n\n:::cell\nCallable annotations are structural: an object with `__call__` can also satisfy the shape.\n\n```python\nclass Doubler:\n def __call__(self, number: int) -> int:\n return number * 2\n\nprint(apply_twice(3, Doubler()))\n```\n\n```output\n12\n```\n:::\n\n:::cell\nRuntime callability is a separate question from static annotation. `callable()` checks whether Python can call the object.\n\n```python\nprint(callable(add_one), callable(Doubler()))\n```\n\n```output\nTrue True\n```\n:::\n\n:::note\n- Use `Callable[[Arg], Return]` for simple function-shaped values.\n- The annotation documents how the callback will be called.\n- For complex call signatures, protocols can be clearer.\n:::\n', 'casts-and-any.md': '+++\nslug = "casts-and-any"\ntitle = "Casts and Any"\nsection = "Types"\nsummary = "Any and cast are escape hatches for places static analysis cannot prove."\ndoc_path = "/library/typing.html#typing.cast"\nsee_also = [\n "type-hints",\n "runtime-type-checks",\n "typed-dicts",\n]\n+++\n\n`Any` and `cast()` are escape hatches. They are useful at messy boundaries where a type checker cannot prove what a value is, but they also remove protection when overused.\n\n`Any` tells static tools to stop checking most operations on a value. `cast(T, value)` tells the type checker to treat a value as `T`, but it returns the same runtime object unchanged.\n\nPrefer narrowing with runtime checks when possible. Use `cast()` when another invariant already proves the type and the checker cannot see that proof.\n\n:::program\n```python\nfrom typing import Any, cast\n\nraw: Any = {"score": "98"}\nscore_text = cast(dict[str, str], raw)["score"]\nscore = int(score_text)\n\nprint(score + 2)\nprint(cast(list[int], raw) is raw)\nprint(type(raw).__name__)\n```\n:::\n\n:::cell\n`Any` disables most static checking for a value. The runtime object is still whatever value was actually assigned.\n\n```python\nfrom typing import Any, cast\n\nraw: Any = {"score": "98"}\nscore_text = cast(dict[str, str], raw)["score"]\nscore = int(score_text)\nprint(score + 2)\n```\n\n```output\n100\n```\n:::\n\n:::cell\n`cast()` does not convert or validate the value. It returns the same object at runtime.\n\n```python\nprint(cast(list[int], raw) is raw)\nprint(type(raw).__name__)\n```\n\n```output\nTrue\ndict\n```\n:::\n\n:::cell\nA real runtime check narrows by inspecting the value. This is safer when the input is untrusted.\n\n```python\nvalue: object = {"score": "98"}\nif isinstance(value, dict):\n print(value["score"])\n```\n\n```output\n98\n```\n:::\n\n:::note\n- `Any` disables most static checking for a value.\n- `cast()` tells the type checker to trust you without changing the runtime object.\n- Prefer narrowing with checks when possible.\n:::\n', 'classes.md': '+++\nslug = "classes"\ntitle = "Classes"\nsection = "Classes"\nsummary = "Classes bundle data and behavior into new object types."\ndoc_path = "/tutorial/classes.html"\nsee_also = [\n "inheritance-and-super",\n "classmethods-and-staticmethods",\n "bound-and-unbound-methods",\n "dataclasses",\n]\n+++\n\nClasses define new object types by bundling data with behavior. They are useful when several values and operations belong together and should travel as one object.\n\nThe alternative is often a dictionary plus separate functions. That is fine for loose data, but a class gives the data a stable API and keeps behavior next to the state it changes.\n\n`__init__` initializes each instance, and methods receive the instance as `self`. Separate instances keep separate state because each object has its own attributes.\n\n:::program\n```python\nclass Counter:\n step = 1\n\n def __init__(self, start=0):\n self.value = start\n\n def increment(self, amount=1):\n self.value += amount\n return self.value\n\nfirst = Counter()\nsecond = Counter(10)\n\nprint(first.value)\nprint(second.value)\nprint(first.increment())\nprint(second.increment(5))\nprint(first.step)\nCounter.step = 5\nprint(second.step)\n\nclass Cart:\n items = []\n\n def add(self, item):\n self.items.append(item)\n\nshared_a = Cart()\nshared_b = Cart()\nshared_a.add("apple")\nprint(shared_b.items)\n\nclass FixedCart:\n def __init__(self):\n self.items = []\n\n def add(self, item):\n self.items.append(item)\n\nown_a = FixedCart()\nown_b = FixedCart()\nown_a.add("apple")\nprint(own_b.items)\n```\n:::\n\n:::cell\nDefine a class when data and behavior should travel together. The initializer gives each object its starting state.\n\n```python\nclass Counter:\n def __init__(self, start=0):\n self.value = start\n\nfirst = Counter()\nsecond = Counter(10)\nprint(first.value)\nprint(second.value)\n```\n\n```output\n0\n10\n```\n:::\n\n:::cell\nMethods are functions attached to the class. `self` is the particular object receiving the method call, so separate instances keep separate state.\n\n```python\nclass Counter:\n def __init__(self, start=0):\n self.value = start\n\n def increment(self, amount=1):\n self.value += amount\n return self.value\n\nfirst = Counter()\nsecond = Counter(10)\nprint(first.increment())\nprint(second.increment(5))\n```\n\n```output\n1\n15\n```\n:::\n\n:::cell\nA name defined directly on the class body is a class attribute, shared by every instance. Reading falls back to the class when the instance has no attribute of that name; assigning to the class itself changes the value for every instance at once.\n\n```python\nclass Counter:\n step = 1\n\n def __init__(self, start=0):\n self.value = start\n\nfirst = Counter()\nsecond = Counter()\nprint(first.step)\nCounter.step = 5\nprint(second.step)\n```\n\n```output\n1\n5\n```\n:::\n\n:::cell\nA mutable class attribute is shared mutable state — the classic footgun. Define per-instance containers in `__init__` so each object owns its own copy.\n\n```python\nclass Cart:\n items = []\n\n def add(self, item):\n self.items.append(item)\n\nshared_a = Cart()\nshared_b = Cart()\nshared_a.add("apple")\nprint(shared_b.items)\n\nclass FixedCart:\n def __init__(self):\n self.items = []\n\n def add(self, item):\n self.items.append(item)\n\nown_a = FixedCart()\nown_b = FixedCart()\nown_a.add("apple")\nprint(own_b.items)\n```\n\n```output\n[\'apple\']\n[]\n```\n:::\n\n:::note\n- `self` is the instance the method is operating on.\n- `__init__` initializes each new object.\n- Class attributes are shared across instances; instance attributes belong to one object.\n- Put mutable defaults in `__init__`, not on the class body.\n- Use classes when behavior belongs with state; use dictionaries for looser structured data.\n:::\n', 'classmethods-and-staticmethods.md': '+++\nslug = "classmethods-and-staticmethods"\ntitle = "Classmethods and Staticmethods"\nsection = "Classes"\nsummary = "Three method shapes: instance, class, and static — each receives a different first argument."\ndoc_path = "/library/functions.html#classmethod"\nsee_also = [\n "classes",\n "decorators",\n "inheritance-and-super",\n]\n+++\n\nA regular method receives the instance as `self`. `@classmethod` makes a method receive the class as `cls` instead, which is the standard shape for alternate constructors. `@staticmethod` removes the implicit first argument entirely, leaving a plain function attached to the class for namespacing.\n\nThe pressure that justifies the decorators is name organization. `Date.from_string("2026-05-09")` reads better than a free-floating `parse_date` function, and `Date.is_leap_year(2024)` keeps the helper next to the class it belongs to even when the helper does not need any class state.\n\nPick instance methods when the work depends on instance state, classmethods when an alternate constructor or class-level operation is the right shape, and staticmethods when the function only happens to live near a class.\n\n:::program\n```python\nclass Date:\n def __init__(self, year, month, day):\n self.year = year\n self.month = month\n self.day = day\n\n def display(self):\n return f"{self.year}-{self.month:02d}-{self.day:02d}"\n\n @classmethod\n def from_string(cls, text):\n year, month, day = (int(part) for part in text.split("-"))\n return cls(year, month, day)\n\n @staticmethod\n def is_leap_year(year):\n return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)\n\ntoday = Date(2026, 5, 9)\nprint(today.display())\n\nlater = Date.from_string("2026-12-31")\nprint(later.display())\n\nprint(Date.is_leap_year(2024))\nprint(Date.is_leap_year(2025))\n\nclass Demo:\n def instance_method(self):\n return type(self).__name__\n\n @classmethod\n def class_method(cls):\n return cls.__name__\n\n @staticmethod\n def static_method():\n return "no receiver"\n\nprint(Demo().instance_method())\nprint(Demo.class_method())\nprint(Demo.static_method())\n```\n:::\n\n:::cell\nAn instance method receives the instance as `self` and reads its state. This is the default and the right shape when the work depends on a particular object\'s data.\n\n```python\nclass Date:\n def __init__(self, year, month, day):\n self.year = year\n self.month = month\n self.day = day\n\n def display(self):\n return f"{self.year}-{self.month:02d}-{self.day:02d}"\n\ntoday = Date(2026, 5, 9)\nprint(today.display())\n```\n\n```output\n2026-05-09\n```\n:::\n\n:::cell\n`@classmethod` makes the method receive the class itself as `cls`. The canonical use is an alternate constructor that parses some other input format and calls `cls(...)`. Because `cls` is the actual class, subclasses calling the same method get an instance of their own type.\n\n```python\nclass Date:\n def __init__(self, year, month, day):\n self.year = year\n self.month = month\n self.day = day\n\n @classmethod\n def from_string(cls, text):\n year, month, day = (int(part) for part in text.split("-"))\n return cls(year, month, day)\n\nlater = Date.from_string("2026-12-31")\nprint(later.year, later.month, later.day)\n```\n\n```output\n2026 12 31\n```\n:::\n\n:::cell\n`@staticmethod` strips the implicit first argument. The function lives on the class for namespacing — like `Date.is_leap_year(2024)` — but does not touch any instance or class state.\n\n```python\nclass Date:\n @staticmethod\n def is_leap_year(year):\n return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)\n\nprint(Date.is_leap_year(2024))\nprint(Date.is_leap_year(2025))\n```\n\n```output\nTrue\nFalse\n```\n:::\n\n:::cell\nSide by side: instance methods receive the instance, classmethods receive the class, staticmethods receive nothing. Classmethods and staticmethods can be called on either the class or an instance.\n\n```python\nclass Demo:\n def instance_method(self):\n return type(self).__name__\n\n @classmethod\n def class_method(cls):\n return cls.__name__\n\n @staticmethod\n def static_method():\n return "no receiver"\n\nprint(Demo().instance_method())\nprint(Demo.class_method())\nprint(Demo.static_method())\n```\n\n```output\nDemo\nDemo\nno receiver\n```\n:::\n\n:::note\n- Instance methods need an instance; classmethods and staticmethods can be called on the class.\n- Use `@classmethod` for alternate constructors and class-level operations that respect subclassing.\n- Use `@staticmethod` only when a function is truly independent of instance and class state but still belongs in the class\'s namespace.\n- A free function is often the right answer when neither decorator applies.\n:::\n', 'closures.md': '+++\nslug = "closures"\ntitle = "Closures"\nsection = "Functions"\nsummary = "Inner functions can remember values from an enclosing scope."\ndoc_path = "/reference/executionmodel.html#binding-of-names"\n+++\n\nA closure is a function that remembers names from the scope where it was created. This lets you configure behavior once and call it later.\n\nEach call to the outer function creates a separate remembered environment. That is why `double` and `triple` can share the same code but keep different factors.\n\nClosures are a foundation for decorators, callbacks, and small function factories.\n\n:::program\n```python\ndef make_multiplier(factor):\n def multiply(value):\n return value * factor\n return multiply\n\ndouble = make_multiplier(2)\nprint(double(5))\n\ntriple = make_multiplier(3)\nprint(triple(5))\n\nlate = []\nfor i in range(3):\n late.append(lambda: i)\nprint([f() for f in late])\n\nbound = []\nfor i in range(3):\n bound.append(lambda i=i: i)\nprint([f() for f in bound])\n```\n:::\n\n:::cell\nDefine a function inside another function when the inner behavior needs to remember setup from the outer call. The returned function keeps access to `factor`.\n\n```python\ndef make_multiplier(factor):\n def multiply(value):\n return value * factor\n return multiply\n\ndouble = make_multiplier(2)\nprint(double(5))\n```\n\n```output\n10\n```\n:::\n\n:::cell\nCalling the outer function again creates a separate closure. `triple` uses the same inner code, but remembers a different `factor`.\n\n```python\ntriple = make_multiplier(3)\nprint(triple(5))\n```\n\n```output\n15\n```\n:::\n\n:::cell\nClosures bind names, not values. Lambdas defined in a loop all reference the same loop variable, so calling them later sees its final value. Capture the value at definition time by binding it as a default argument — `lambda i=i: i` — so each closure remembers its own `i`.\n\n```python\nlate = []\nfor i in range(3):\n late.append(lambda: i)\nprint([f() for f in late])\n\nbound = []\nfor i in range(3):\n bound.append(lambda i=i: i)\nprint([f() for f in bound])\n```\n\n```output\n[2, 2, 2]\n[0, 1, 2]\n```\n:::\n\n:::note\n- A closure keeps access to names from the scope where the inner function was created.\n- Each call to the outer function can create a separate remembered environment.\n- Closures are useful for callbacks, small factories, and decorators.\n- Closures bind names, not values; capture loop variables with `lambda x=x: ...` to freeze them at definition time.\n:::\n', 'collections-module.md': '+++\nslug = "collections-module"\ntitle = "Collections Module"\nsection = "Collections"\nsummary = "collections provides specialized containers for common data shapes."\ndoc_path = "/library/collections.html"\n+++\n\ncollections provides specialized containers for common data shapes. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.\n\nUse it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.\n\nThe example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.\n\n:::program\n```python\nfrom collections import Counter, defaultdict, deque, namedtuple\n\ncounts = Counter("banana")\nprint(counts.most_common(2))\n\ngroups = defaultdict(list)\nfor name, team in [("Ada", "red"), ("Grace", "blue"), ("Lin", "red")]:\n groups[team].append(name)\nprint(dict(groups))\n\nqueue = deque(["first"])\nqueue.append("second")\nprint(queue.popleft())\n\nPoint = namedtuple("Point", "x y")\nprint(Point(2, 3).x)\n```\n:::\n\n:::cell\nUse `Counter` when counting is the data shape.\n\n```python\nfrom collections import Counter, defaultdict, deque, namedtuple\n\ncounts = Counter("banana")\nprint(counts.most_common(2))\n\ngroups = defaultdict(list)\nfor name, team in [("Ada", "red"), ("Grace", "blue"), ("Lin", "red")]:\n groups[team].append(name)\nprint(dict(groups))\n\nqueue = deque(["first"])\nqueue.append("second")\nprint(queue.popleft())\n\nPoint = namedtuple("Point", "x y")\nprint(Point(2, 3).x)\n```\n\n```output\n[(\'a\', 3), (\'n\', 2)]\n{\'red\': [\'Ada\', \'Lin\'], \'blue\': [\'Grace\']}\nfirst\n2\n```\n:::\n\n:::note\n- Use `Counter` when counting is the data shape.\n- Use `defaultdict` when grouping values by key.\n- Use `deque` for efficient queue operations and `namedtuple` for lightweight named records.\n:::\n', 'comprehension-patterns.md': '+++\nslug = "comprehension-patterns"\ntitle = "Comprehension Patterns"\nsection = "Collections"\nsummary = "Comprehensions can use multiple for clauses and filters when the shape stays clear."\ndoc_path = "/tutorial/datastructures.html#list-comprehensions"\nsee_also = [\n "comprehensions",\n "generator-expressions",\n "for-loops",\n]\n+++\n\nComprehensions can contain more than one `for` clause and more than one `if` filter. The clauses are read in the same order as nested loops.\n\nUse these forms only while the shape remains easy to scan. If a comprehension starts needing several names, comments, or branches, an explicit loop is usually better.\n\nNested comprehensions build concrete collections immediately, just like simpler list, dict, and set comprehensions.\n\n:::program\n```python\ncolors = ["red", "blue"]\nsizes = ["S", "M"]\nvariants = [(color, size) for color in colors for size in sizes]\nprint(variants)\n\nnumbers = range(10)\nfiltered = [n for n in numbers if n % 2 == 0 if n > 2]\nprint(filtered)\n```\n:::\n\n:::cell\nMultiple `for` clauses behave like nested loops. The leftmost `for` is the outer loop, and the next `for` runs inside it.\n\n```python\ncolors = ["red", "blue"]\nsizes = ["S", "M"]\nvariants = [(color, size) for color in colors for size in sizes]\nprint(variants)\n```\n\n```output\n[(\'red\', \'S\'), (\'red\', \'M\'), (\'blue\', \'S\'), (\'blue\', \'M\')]\n```\n:::\n\n:::cell\nMultiple `if` clauses filter values. They are useful for simple conditions, but an explicit loop is clearer when the rules need names or explanation.\n\n```python\nnumbers = range(10)\nfiltered = [n for n in numbers if n % 2 == 0 if n > 2]\nprint(filtered)\n```\n\n```output\n[4, 6, 8]\n```\n:::\n\n:::note\n- Read comprehension clauses from left to right.\n- Multiple `for` clauses act like nested loops.\n- Prefer an explicit loop when the comprehension stops being obvious.\n:::\n', 'comprehensions.md': '+++\nslug = "comprehensions"\ntitle = "Comprehensions"\nsection = "Collections"\nsummary = "Comprehensions build collections by mapping and filtering iterables."\ndoc_path = "/tutorial/datastructures.html#list-comprehensions"\n+++\n\nComprehensions are expression forms for building concrete collections from iterables. Read them from left to right: produce this value, for each item, optionally only when a condition is true.\n\nThey are best for direct transformations where the expression is still easy to scan. When the work needs several statements or names, an explicit loop is usually clearer.\n\nList, dictionary, and set comprehensions are eager: they build collections immediately. Generator expressions use similar syntax to stream values later and are covered in the Iteration section.\n\n:::program\n```python\nnames = ["ada", "guido", "grace"]\ntitled = [name.title() for name in names]\nprint(titled)\n\nscores = {"Ada": 10, "Guido": 8, "Grace": 10}\nhigh_scores = {name: score for name, score in scores.items() if score >= 10}\nprint(high_scores)\n\nunique_scores = {score for score in scores.values()}\nprint(unique_scores)\n```\n:::\n\n:::cell\nA list comprehension maps each input item to one output item. This one calls `title()` for every name and collects the results in a new list.\n\n```python\nnames = ["ada", "guido", "grace"]\ntitled = [name.title() for name in names]\nprint(titled)\n```\n\n```output\n[\'Ada\', \'Guido\', \'Grace\']\n```\n:::\n\n:::cell\nAdd an `if` clause when only some items should appear. A dictionary comprehension can transform key/value pairs while preserving the dictionary shape.\n\n```python\nscores = {"Ada": 10, "Guido": 8, "Grace": 10}\nhigh_scores = {name: score for name, score in scores.items() if score >= 10}\nprint(high_scores)\n```\n\n```output\n{\'Ada\': 10, \'Grace\': 10}\n```\n:::\n\n:::cell\nA set comprehension keeps only unique results. Here two people have the same score, so the resulting set has two values.\n\n```python\nunique_scores = {score for score in scores.values()}\nprint(unique_scores)\n```\n\n```output\n{8, 10}\n```\n:::\n\n:::note\n- The left side says what to produce; the `for` clause says where values come from.\n- Use an `if` clause for simple filters.\n- List, dict, and set comprehensions build concrete collections immediately.\n- Switch to a loop when the transformation needs multiple steps or explanations.\n:::\n', 'conditionals.md': '+++\nslug = "conditionals"\ntitle = "Conditionals"\nsection = "Control Flow"\nsummary = "if, elif, and else choose which block runs."\ndoc_path = "/tutorial/controlflow.html#if-statements"\n+++\n\n`if`, `elif`, and `else` let a program choose one path based on a condition. Python uses indentation to show which statements belong to each branch.\n\nConditions use Python truthiness: booleans work directly, and many objects such as empty lists or empty strings are considered false. Order branches from most specific to most general.\n\nUse `elif` to keep one decision flat instead of nested. Use Python\'s ternary expression only when you are choosing between two values.\n\n:::program\n```python\ntemperature = 72\n\nif temperature < 60:\n print("cold")\nelif temperature < 80:\n print("comfortable")\nelse:\n print("hot")\n\nitems = ["coat", "hat"]\nif items:\n print(f"packing {len(items)} items")\n\nstatus = "ok" if temperature < 90 else "danger"\nprint(status)\n```\n:::\n\n:::cell\nStart with the value that the branches will test. A conditional is only useful when the branch condition is visible and meaningful.\n\nUse `if`, `elif`, and `else` for one ordered choice. Python tests the branches from top to bottom and runs only the first matching block.\n\n```python\ntemperature = 72\n\nif temperature < 60:\n print("cold")\nelif temperature < 80:\n print("comfortable")\nelse:\n print("hot")\n```\n\n```output\ncomfortable\n```\n:::\n\n:::cell\nTruthiness is part of conditional flow. Empty collections are false, so `if items:` reads as “if there is anything to work with.”\n\n```python\nitems = ["coat", "hat"]\nif items:\n print(f"packing {len(items)} items")\n```\n\n```output\npacking 2 items\n```\n:::\n\n:::cell\nUse the ternary expression when you are choosing a value. If either side needs multiple statements, use a normal `if` block instead.\n\n```python\nstatus = "ok" if temperature < 90 else "danger"\nprint(status)\n```\n\n```output\nok\n```\n:::\n\n:::note\n- Python has no mandatory parentheses around conditions; the colon and indentation define the block.\n- Comparison operators such as `<` and `==` can be chained, as in `0 < value < 10`.\n- Keep branch bodies short; move larger work into functions so the decision remains easy to scan.\n:::\n', 'constants.md': '+++\nslug = "constants"\ntitle = "Constants"\nsection = "Basics"\nsummary = "Python uses naming conventions for values that should not change."\ndoc_path = "/tutorial/classes.html#python-scopes-and-namespaces"\n+++\n\nPython has no `const` keyword for ordinary variables. Instead, modules use all-caps names to mark values that should be treated as constants by convention.\n\nThe interpreter will not stop rebinding, but the convention is important API communication. Readers understand that `MAX_RETRIES` is configuration, not loop state.\n\nNamed constants remove magic values from code and give repeated literals one place to change.\n\n:::program\n```python\nMAX_RETRIES = 3\nAPI_VERSION = "2026-05"\n\nfor attempt in range(1, MAX_RETRIES + 1):\n print(f"attempt {attempt} of {MAX_RETRIES}")\n\nprint(API_VERSION)\n```\n:::\n\n:::cell\nPython does not have a `const` declaration like Go or Rust. Instead, modules use all-caps names for values callers should treat as fixed.\n\nThe interpreter will still let you rebind the name, but the convention is strong enough that readers understand the design intent.\n\n```python\nMAX_RETRIES = 3\nAPI_VERSION = "2026-05"\n\nfor attempt in range(1, MAX_RETRIES + 1):\n print(f"attempt {attempt} of {MAX_RETRIES}")\n```\n\n```output\nattempt 1 of 3\nattempt 2 of 3\nattempt 3 of 3\n```\n:::\n\n:::cell\nConstants are useful for configuration values that should be named once and reused instead of repeated as magic literals.\n\n```python\nprint(API_VERSION)\n```\n\n```output\n2026-05\n```\n:::\n\n:::note\n- Python has no `const` keyword for ordinary names.\n- All-caps names such as `MAX_RETRIES` communicate that a value is intended to stay fixed.\n:::\n', 'container-protocols.md': '+++\nslug = "container-protocols"\ntitle = "Container Protocols"\nsection = "Data Model"\nsummary = "Container methods connect objects to indexing, membership, and item assignment."\ndoc_path = "/reference/datamodel.html#emulating-container-types"\nsee_also = [\n "lists",\n "dicts",\n "special-methods",\n]\n+++\n\nContainer protocols let a class behave like the collection it represents. Instead of inventing method names such as `has()` or `lookup()`, the object can support `in`, indexing, and assignment.\n\nThe key methods are small and familiar: `__contains__` powers `in`, `__getitem__` powers `obj[key]`, and `__setitem__` powers `obj[key] = value`. Add only the operations the object can honestly support.\n\nThis keeps the public interface aligned with Python\'s built-in containers. Callers can use the same syntax for custom records, caches, tables, and sequence-like objects.\n\n:::program\n```python\nclass Scores:\n def __init__(self):\n self._scores = {}\n\n def __contains__(self, name):\n return name in self._scores\n\n def __getitem__(self, name):\n return self._scores[name]\n\n def __setitem__(self, name, score):\n self._scores[name] = score\n\nscores = Scores()\nscores["Ada"] = 98\nprint("Ada" in scores)\nprint(scores["Ada"])\n```\n:::\n\n:::cell\n`__setitem__` gives assignment syntax to a custom container.\n\n```python\nclass Scores:\n def __init__(self):\n self._scores = {}\n\n def __setitem__(self, name, score):\n self._scores[name] = score\n\nscores = Scores()\nscores["Ada"] = 98\nprint(scores._scores)\n```\n\n```output\n{\'Ada\': 98}\n```\n:::\n\n:::cell\n`__contains__` answers membership tests written with `in`.\n\n```python\nclass Scores:\n def __init__(self):\n self._scores = {"Ada": 98}\n\n def __contains__(self, name):\n return name in self._scores\n\nscores = Scores()\nprint("Ada" in scores)\n```\n\n```output\nTrue\n```\n:::\n\n:::cell\n`__getitem__` connects bracket lookup to your internal storage.\n\n```python\nclass Scores:\n def __init__(self):\n self._scores = {"Ada": 98}\n\n def __getitem__(self, name):\n return self._scores[name]\n\nscores = Scores()\nprint(scores["Ada"])\n```\n\n```output\n98\n```\n:::\n\n:::note\n- Implement the narrowest container protocol your object needs.\n- Use `KeyError` and `IndexError` consistently with built-in containers.\n- If a plain `dict` or `list` is enough, prefer it over a custom container.\n:::\n', 'context-managers.md': '+++\nslug = "context-managers"\ntitle = "Context Managers"\nsection = "Data Model"\nsummary = "with ensures setup and cleanup happen together."\ndoc_path = "/reference/datamodel.html#context-managers"\nsee_also = [\n "exceptions",\n "special-methods",\n "descriptors",\n]\n+++\n\nContext managers define setup and cleanup around a block of code. The `with` statement guarantees that cleanup runs when the block exits, even when an exception is raised.\n\nThe protocol is powered by `__enter__` and `__exit__`. The `contextlib.contextmanager` decorator is a concise way to write the same idea as a generator when a full class would be noisy.\n\nProduction code often uses `with` for files, locks, transactions, temporary state, and resources that need reliable release.\n\n:::program\n```python\nfrom contextlib import contextmanager\n\nclass Tag:\n def __init__(self, name):\n self.name = name\n\n def __enter__(self):\n print(f"<{self.name}>")\n return self\n\n def __exit__(self, exc_type, exc, tb):\n print(f"{self.name}>")\n return False\n\n@contextmanager\ndef tag(name):\n print(f"<{name}>")\n try:\n yield\n finally:\n print(f"{name}>")\n\nwith Tag("section"):\n print("content")\n\ntry:\n with tag("error"):\n raise ValueError("boom")\nexcept ValueError:\n print("handled")\n```\n:::\n\n:::cell\nA class-based context manager implements `__enter__` and `__exit__`. The value returned by `__enter__` is bound by `as` when the `with` statement uses it.\n\n```python\nclass Tag:\n def __init__(self, name):\n self.name = name\n\n def __enter__(self):\n print(f"<{self.name}>")\n return self\n\n def __exit__(self, exc_type, exc, tb):\n print(f"{self.name}>")\n return False\n\nwith Tag("section"):\n print("content")\n```\n\n```output\n\n```\n:::\n\n:::cell\n`contextlib.contextmanager` writes the same setup/cleanup shape as a generator. Code before `yield` is setup, and code after `yield` is cleanup.\n\n```python\nfrom contextlib import contextmanager\n\n@contextmanager\ndef tag(name):\n print(f"<{name}>")\n try:\n yield\n finally:\n print(f"{name}>")\n\nwith tag("note"):\n print("body")\n```\n\n```output\n\nbody\n\n```\n:::\n\n:::cell\nCleanup still runs when the block raises. Returning `False` from `__exit__`, or letting a generator context manager re-raise, allows the exception to keep propagating.\n\n```python\ntry:\n with tag("error"):\n raise ValueError("boom")\nexcept ValueError:\n print("handled")\n```\n\n```output\n\n\nhandled\n```\n:::\n\n:::note\n- Files, locks, and temporary state commonly use context managers.\n- `__enter__` and `__exit__` power the protocol.\n- Use `finally` when cleanup must happen after errors too.\n- Returning true from `__exit__` suppresses an exception; do that only intentionally.\n:::\n', 'copying-collections.md': '+++\nslug = "copying-collections"\ntitle = "Copying Collections"\nsection = "Collections"\nsummary = "Copies can duplicate a container while still sharing nested objects."\ndoc_path = "/library/copy.html"\n+++\n\nCopies can duplicate a container while still sharing nested objects. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.\n\nUse it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.\n\nThe example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.\n\n:::program\n```python\nimport copy\n\nrows = [["Ada"], ["Grace"]]\nshallow = rows.copy()\ndeep = copy.deepcopy(rows)\n\nrows[0].append("Lovelace")\n\nprint(shallow)\nprint(deep)\nprint(rows[0] is shallow[0])\nprint(rows[0] is deep[0])\n```\n:::\n\n:::cell\nA shallow copy makes a new outer container.\n\n```python\nimport copy\n\nrows = [["Ada"], ["Grace"]]\nshallow = rows.copy()\ndeep = copy.deepcopy(rows)\n\nrows[0].append("Lovelace")\n\nprint(shallow)\nprint(deep)\nprint(rows[0] is shallow[0])\nprint(rows[0] is deep[0])\n```\n\n```output\n[[\'Ada\', \'Lovelace\'], [\'Grace\']]\n[[\'Ada\'], [\'Grace\']]\nTrue\nFalse\n```\n:::\n\n:::note\n- A shallow copy makes a new outer container.\n- Nested objects are still shared by a shallow copy.\n- Use `copy.deepcopy()` only when nested independence is required.\n:::\n', 'csv-data.md': '+++\nslug = "csv-data"\ntitle = "CSV Data"\nsection = "Standard Library"\nsummary = "csv reads and writes row-shaped text data."\ndoc_path = "/library/csv.html"\n+++\n\ncsv reads and writes row-shaped text data. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.\n\nUse it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.\n\nThe example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.\n\n:::program\n```python\nimport csv\nimport io\n\ntext = "name,score\\nAda,98\\nGrace,95\\n"\nreader = csv.DictReader(io.StringIO(text))\nrows = list(reader)\n\nprint(rows[0]["name"])\nprint(sum(int(row["score"]) for row in rows))\n\noutput = io.StringIO()\nwriter = csv.DictWriter(output, fieldnames=["name", "passed"])\nwriter.writeheader()\nwriter.writerow({"name": "Ada", "passed": True})\nprint(output.getvalue().splitlines()[1])\n```\n:::\n\n:::cell\nUse `DictReader` when column names should become dictionary keys.\n\n```python\nimport csv\nimport io\n\ntext = "name,score\\nAda,98\\nGrace,95\\n"\nreader = csv.DictReader(io.StringIO(text))\nrows = list(reader)\n\nprint(rows[0]["name"])\nprint(sum(int(row["score"]) for row in rows))\n\noutput = io.StringIO()\nwriter = csv.DictWriter(output, fieldnames=["name", "passed"])\nwriter.writeheader()\nwriter.writerow({"name": "Ada", "passed": True})\nprint(output.getvalue().splitlines()[1])\n```\n\n```output\nAda\n193\nAda,True\n```\n:::\n\n:::note\n- Use `DictReader` when column names should become dictionary keys.\n- CSV fields arrive as text, so convert numbers explicitly.\n- `DictWriter` writes dictionaries back to row-shaped text.\n:::\n', 'custom-exceptions.md': '+++\nslug = "custom-exceptions"\ntitle = "Custom Exceptions"\nsection = "Errors"\nsummary = "Custom exception classes name failures that belong to your domain."\ndoc_path = "/tutorial/errors.html#user-defined-exceptions"\n+++\n\nCustom exceptions give names to failures in your problem domain. A named exception is easier to catch and explain than a generic error with only a string message.\n\nRaise the custom exception at the point where the invalid state is discovered. Include a message for the specific occurrence.\n\nCatch custom exceptions at the boundary where recovery makes sense, such as returning an error response or asking for corrected input.\n\n:::program\n```python\nclass EmptyCartError(Exception):\n pass\n\nprint(EmptyCartError.__name__)\n\n\ndef checkout(items):\n if not items:\n raise EmptyCartError("cart is empty")\n return "paid"\n\nprint(checkout(["book"]))\n\ntry:\n checkout([])\nexcept EmptyCartError as error:\n print(error)\n```\n:::\n\n:::cell\nCreate a custom exception when a failure has a name in your problem domain. The class can be empty at first.\n\n```python\nclass EmptyCartError(Exception):\n pass\n\nprint(EmptyCartError.__name__)\n```\n\n```output\nEmptyCartError\n```\n:::\n\n:::cell\nRaise the custom exception where the invalid state is detected. Normal inputs still follow the ordinary success path.\n\n```python\ndef checkout(items):\n if not items:\n raise EmptyCartError("cart is empty")\n return "paid"\n\nprint(checkout(["book"]))\n```\n\n```output\npaid\n```\n:::\n\n:::cell\nCallers can catch the precise error type without accidentally catching unrelated failures.\n\n```python\ntry:\n checkout([])\nexcept EmptyCartError as error:\n print(error)\n```\n\n```output\ncart is empty\n```\n:::\n\n:::note\n- Subclass `Exception` for errors callers are expected to catch.\n- A custom exception name can be clearer than reusing a generic `ValueError` everywhere.\n- Catch custom exceptions at a boundary that can recover or report clearly.\n:::\n', 'dataclasses.md': '+++\nslug = "dataclasses"\ntitle = "Dataclasses"\nsection = "Classes"\nsummary = "dataclass generates common class methods for data containers."\ndoc_path = "/library/dataclasses.html"\nsee_also = [\n "structured-data-shapes",\n "classes",\n "type-hints",\n]\n+++\n\n`dataclass` is a standard-library decorator for classes that mainly store data. It generates methods such as `__init__` and `__repr__` from type-annotated fields.\n\nDataclasses reduce boilerplate while keeping classes explicit. They are a good fit for simple records, configuration objects, and values passed between layers.\n\nType annotations define fields. Defaults work like normal class attributes and appear in the generated initializer.\n\n:::program\n```python\nfrom dataclasses import dataclass\n\n@dataclass\nclass User:\n name: str\n active: bool = True\n\nuser = User("Ada")\nprint(user)\nprint(user.name)\n\ninactive = User("Guido", active=False)\nprint(inactive)\nprint(inactive.active)\n```\n:::\n\n:::cell\nA dataclass uses annotations to define fields. Python generates an initializer, so the class can be constructed without writing `__init__` by hand.\n\n```python\nfrom dataclasses import dataclass\n\n@dataclass\nclass User:\n name: str\n active: bool = True\n\nuser = User("Ada")\nprint(user)\n```\n\n```output\nUser(name=\'Ada\', active=True)\n```\n:::\n\n:::cell\nThe generated instance still exposes ordinary attributes. A dataclass is a regular class with useful methods filled in.\n\n```python\nprint(user.name)\n```\n\n```output\nAda\n```\n:::\n\n:::cell\nDefaults can be overridden by keyword. The generated representation includes the field names, which is useful during debugging.\n\n```python\ninactive = User("Guido", active=False)\nprint(inactive)\nprint(inactive.active)\n```\n\n```output\nUser(name=\'Guido\', active=False)\nFalse\n```\n:::\n\n:::note\n- Type annotations define dataclass fields.\n- Dataclasses generate methods but remain normal Python classes.\n- Use `field()` for advanced defaults such as per-instance lists or dictionaries.\n:::\n', 'datetime.md': '+++\nslug = "datetime"\ntitle = "Dates and Times"\nsection = "Standard Library"\nsummary = "datetime represents dates, times, durations, formatting, and parsing."\ndoc_path = "/library/datetime.html"\n+++\n\nThe `datetime` module covers several related ideas: `date` for calendar days, `time` for clock times, `datetime` for both together, and `timedelta` for durations.\n\nTimezone-aware datetimes avoid ambiguity in real systems. `timezone.utc` is a clear default for examples because output stays stable and portable.\n\nUse ISO formatting for interchange, `strftime()` for display, and parsing helpers such as `fromisoformat()` to turn text back into datetime objects.\n\n:::program\n```python\nfrom datetime import date, datetime, time, timedelta, timezone\n\nrelease_day = date(2026, 5, 4)\nmeeting_time = time(12, 30)\ncreated_at = datetime.combine(release_day, meeting_time, tzinfo=timezone.utc)\n\nprint(release_day.isoformat())\nprint(meeting_time.isoformat())\nprint(created_at.isoformat())\n\nexpires_at = created_at + timedelta(days=7, hours=2)\nprint(expires_at.isoformat())\n\nprint(created_at.strftime("%Y-%m-%d %H:%M %Z"))\nparsed = datetime.fromisoformat("2026-05-04T12:30:00+00:00")\nprint(parsed == created_at)\n```\n:::\n\n:::cell\nThe `datetime` module separates calendar dates, clock times, combined datetimes, and durations. Import the types you need explicitly.\n\nUse `date` for a calendar day and `time` for a time of day. Combine them into a timezone-aware `datetime` when you mean an instant.\n\n`isoformat()` produces stable machine-readable text. It is a good default for examples, APIs, and logs.\n\n```python\nfrom datetime import date, datetime, time, timedelta, timezone\n\nrelease_day = date(2026, 5, 4)\nmeeting_time = time(12, 30)\ncreated_at = datetime.combine(release_day, meeting_time, tzinfo=timezone.utc)\n\nprint(release_day.isoformat())\nprint(meeting_time.isoformat())\nprint(created_at.isoformat())\n```\n\n```output\n2026-05-04\n12:30:00\n2026-05-04T12:30:00+00:00\n```\n:::\n\n:::cell\nUse `timedelta` for durations. Adding one to a `datetime` produces another `datetime` without manually changing calendar fields.\n\n```python\nexpires_at = created_at + timedelta(days=7, hours=2)\nprint(expires_at.isoformat())\n```\n\n```output\n2026-05-11T14:30:00+00:00\n```\n:::\n\n:::cell\nUse `strftime()` for human-facing formatting and `fromisoformat()` when reading ISO 8601 text back into a `datetime`.\n\n```python\nprint(created_at.strftime("%Y-%m-%d %H:%M %Z"))\nparsed = datetime.fromisoformat("2026-05-04T12:30:00+00:00")\nprint(parsed == created_at)\n```\n\n```output\n2026-05-04 12:30 UTC\nTrue\n```\n:::\n\n:::note\n- Use timezone-aware datetimes for instants that cross system or user boundaries.\n- Use `date` for calendar days, `time` for clock times, `datetime` for both, and `timedelta` for durations.\n- Prefer ISO 8601 strings for interchange; use `strftime` for human-facing display.\n:::\n', 'decorators.md': '+++\nslug = "decorators"\ntitle = "Decorators"\nsection = "Functions"\nsummary = "Decorators wrap or register functions using @ syntax."\ndoc_path = "/glossary.html#term-decorator"\nsee_also = [\n "closures",\n "functions",\n "callable-types",\n "classmethods-and-staticmethods",\n]\n+++\n\nA decorator is a callable that receives a function and returns a replacement. The `@` syntax applies that transformation at function definition time.\n\nDecorators are common in frameworks because they can register handlers or add behavior while keeping the decorated function focused on the core action.\n\n`@decorator` is shorthand for rebinding a function to the decorator\'s return value. Production wrappers usually use `functools.wraps` so debugging, help text, and framework introspection still see the original function metadata.\n\n:::program\n```python\nfrom functools import wraps\n\n\ndef loud(func):\n @wraps(func)\n def wrapper(name):\n return func(name).upper()\n return wrapper\n\n\ndef greet(name):\n return f"hello {name}"\n\nmanual_greet = loud(greet)\nprint(manual_greet("python"))\n\n@loud\ndef welcome(name):\n """Return a welcome message."""\n return f"welcome {name}"\n\nprint(welcome("workers"))\nprint(welcome.__name__)\n```\n:::\n\n:::cell\nA decorator is just a function that takes a function and returns another callable. Applying it manually shows the wrapping step.\n\n```python\nfrom functools import wraps\n\n\ndef loud(func):\n @wraps(func)\n def wrapper(name):\n return func(name).upper()\n return wrapper\n\n\ndef greet(name):\n return f"hello {name}"\n\nmanual_greet = loud(greet)\nprint(manual_greet("python"))\n```\n\n```output\nHELLO PYTHON\n```\n:::\n\n:::cell\nThe `@loud` syntax performs the same rebinding at definition time. After decoration, `welcome` refers to the wrapper returned by `loud`.\n\n```python\n@loud\ndef welcome(name):\n """Return a welcome message."""\n return f"welcome {name}"\n\nprint(welcome("workers"))\n```\n\n```output\nWELCOME WORKERS\n```\n:::\n\n:::cell\n`functools.wraps` copies useful metadata from the original function onto the wrapper.\n\n```python\nprint(welcome.__name__)\nprint(welcome.__doc__)\n```\n\n```output\nwelcome\nReturn a welcome message.\n```\n:::\n\n:::note\n- `@decorator` is shorthand for assigning `func = decorator(func)`.\n- Decorators can wrap, replace, or register functions.\n- Use `functools.wraps` in production wrappers that should preserve metadata.\n:::\n', 'delete-statements.md': '+++\nslug = "delete-statements"\ntitle = "Delete Statements"\nsection = "Data Model"\nsummary = "del removes bindings, items, and attributes rather than producing a value."\ndoc_path = "/reference/simple_stmts.html#the-del-statement"\nsee_also = [\n "variables",\n "dicts",\n "mutability",\n]\n+++\n\n`del` removes a binding or an item. It is a statement, not a function, and it does not return the removed value.\n\nUse `del name` when a name should no longer be bound. Use `del mapping[key]` or `del sequence[index]` when mutating a container by removing one part.\n\nThis is different from assigning `None`: `None` is still a value, while `del` removes the binding or slot.\n\n:::program\n```python\nprofile = {"name": "Ada", "temporary": True}\ndel profile["temporary"]\nprint(profile)\n\nitems = ["a", "b", "c"]\ndel items[1]\nprint(items)\n\nvalue = "cached"\ndel value\nprint("value" in locals())\n```\n:::\n\n:::cell\nDeleting a dictionary key mutates the dictionary. The key is gone; it has not been set to `None`.\n\n```python\nprofile = {"name": "Ada", "temporary": True}\ndel profile["temporary"]\nprint(profile)\n```\n\n```output\n{\'name\': \'Ada\'}\n```\n:::\n\n:::cell\nDeleting a list item removes that position and shifts later items left.\n\n```python\nitems = ["a", "b", "c"]\ndel items[1]\nprint(items)\n```\n\n```output\n[\'a\', \'c\']\n```\n:::\n\n:::cell\nDeleting a name removes the binding from the current namespace. It is different from rebinding the name to `None`.\n\n```python\nvalue = "cached"\ndel value\nprint("value" in locals())\n```\n\n```output\nFalse\n```\n:::\n\n:::note\n- `del` removes bindings or container entries.\n- Assign `None` when absence should remain an explicit value.\n- Use container methods such as `pop()` when you need the removed value back.\n:::\n', 'descriptors.md': '+++\nslug = "descriptors"\ntitle = "Descriptors"\nsection = "Data Model"\nsummary = "Descriptors customize attribute access through __get__, __set__, or __delete__."\ndoc_path = "/howto/descriptor.html"\nsee_also = [\n "attribute-access",\n "properties",\n "bound-and-unbound-methods",\n]\n+++\n\nDescriptors customize attribute access through __get__, __set__, or __delete__. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.\n\nUse it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.\n\nThe example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.\n\n:::program\n```python\nclass Positive:\n def __set_name__(self, owner, name):\n self.private_name = "_" + name\n\n def __get__(self, obj, owner):\n return getattr(obj, self.private_name)\n\n def __set__(self, obj, value):\n if value <= 0:\n raise ValueError("must be positive")\n setattr(obj, self.private_name, value)\n\nclass Product:\n price = Positive()\n\n def __init__(self, price):\n self.price = price\n\nitem = Product(10)\nprint(item.price)\ntry:\n item.price = -1\nexcept ValueError as error:\n print(error)\n```\n:::\n\n:::cell\nA descriptor object lives on the class.\n\n```python\nclass Positive:\n def __set_name__(self, owner, name):\n self.private_name = "_" + name\n\n def __get__(self, obj, owner):\n return getattr(obj, self.private_name)\n\n def __set__(self, obj, value):\n if value <= 0:\n raise ValueError("must be positive")\n setattr(obj, self.private_name, value)\n\nclass Product:\n price = Positive()\n\n def __init__(self, price):\n self.price = price\n\nitem = Product(10)\nprint(item.price)\ntry:\n item.price = -1\nexcept ValueError as error:\n print(error)\n```\n\n```output\n10\nmust be positive\n```\n:::\n\n:::note\n- A descriptor object lives on the class.\n- Attribute access on instances calls descriptor methods.\n- Properties, methods, and many ORMs build on the descriptor protocol.\n:::\n', 'dicts.md': '+++\nslug = "dicts"\ntitle = "Dictionaries"\nsection = "Collections"\nsummary = "Dictionaries map keys to values for records, lookup, and structured data."\ndoc_path = "/tutorial/datastructures.html#dictionaries"\n+++\n\nDictionaries are Python\'s built-in mapping type. They exist for data where names or keys are more meaningful than numeric positions: records, lookup tables, counters, and JSON-like payloads.\n\nUse direct indexing when a key is required. Use `get()` when absence is expected and the code has a reasonable fallback.\n\nUnlike lists, dictionaries answer “what value belongs to this key?” rather than “what value is at this position?” Iterating with `items()` keeps each key next to its value.\n\n:::program\n```python\nprofile = {"name": "Ada", "language": "Python"}\nprofile["year"] = 1843\nprint(profile["name"])\nprint(profile.get("timezone", "UTC"))\n\nscores = {"Ada": 10, "Grace": 9}\nprint(scores["Grace"])\nprint(scores.get("Guido", 0))\n\nfor name, score in scores.items():\n print(f"{name}: {score}")\n\ninventory = {"apple": 0, "pear": 3, "plum": 0}\nfor name in list(inventory.keys()):\n if inventory[name] == 0:\n del inventory[name]\nprint(inventory)\n```\n:::\n\n:::cell\nUse a dictionary as a small record when fields have names. Direct indexing communicates that the key is required, while `get()` communicates that a missing key has a fallback.\n\n```python\nprofile = {"name": "Ada", "language": "Python"}\nprofile["year"] = 1843\nprint(profile["name"])\nprint(profile.get("timezone", "UTC"))\n```\n\n```output\nAda\nUTC\n```\n:::\n\n:::cell\nUse a dictionary as a lookup table when keys identify values. This is different from a list, where numeric position is the lookup key.\n\n```python\nscores = {"Ada": 10, "Grace": 9}\nprint(scores["Grace"])\nprint(scores.get("Guido", 0))\n```\n\n```output\n9\n0\n```\n:::\n\n:::cell\nUse `items()` when the loop needs both keys and values. It avoids looping over keys and then indexing back into the dictionary.\n\n```python\nfor name, score in scores.items():\n print(f"{name}: {score}")\n```\n\n```output\nAda: 10\nGrace: 9\n```\n:::\n\n:::cell\nMutating a dictionary while iterating it raises `RuntimeError`. Snapshot the keys with `list(d.keys())` (or build a list of changes and apply them after the loop) so the iteration sees a stable view.\n\n```python\ninventory = {"apple": 0, "pear": 3, "plum": 0}\nfor name in list(inventory.keys()):\n if inventory[name] == 0:\n del inventory[name]\nprint(inventory)\n```\n\n```output\n{\'pear\': 3}\n```\n:::\n\n:::note\n- Dictionaries preserve insertion order in modern Python.\n- Use `get()` when a missing key has a reasonable default.\n- Use direct indexing when a missing key should be treated as an error.\n- Snapshot keys with `list(d.keys())` before deleting items in a loop; mutating during iteration raises `RuntimeError`.\n:::\n', 'enums.md': '+++\nslug = "enums"\ntitle = "Enums"\nsection = "Types"\nsummary = "Enum defines symbolic names for a fixed set of values."\ndoc_path = "/library/enum.html"\n+++\n\n`Enum` defines a fixed set of named values. This makes states and modes easier to read than raw strings scattered through a program.\n\nEach enum member has a name and a value. Comparing enum members is explicit and helps avoid typos that plain strings would allow.\n\nUse enums when a value must be one of a small known set: statuses, modes, directions, roles, and similar choices.\n\n:::program\n```python\nfrom enum import Enum\n\nclass Status(Enum):\n PENDING = "pending"\n DONE = "done"\n\ncurrent = Status.PENDING\nprint(current.name)\nprint(current.value)\nprint(current is Status.PENDING)\nprint(current == "pending")\n```\n:::\n\n:::cell\nAn enum member has a symbolic name and an underlying value. The symbolic name is what readers usually care about in code.\n\n```python\nfrom enum import Enum\n\nclass Status(Enum):\n PENDING = "pending"\n DONE = "done"\n\ncurrent = Status.PENDING\nprint(current.name)\nprint(current.value)\n```\n\n```output\nPENDING\npending\n```\n:::\n\n:::cell\nCompare enum members with enum members, not with raw strings. This keeps the set of valid states explicit.\n\n```python\nprint(current is Status.PENDING)\nprint(current == "pending")\n```\n\n```output\nTrue\nFalse\n```\n:::\n\n:::note\n- Enums make states and choices explicit.\n- Members have names and values.\n- Comparing enum members avoids string typo bugs.\n- Prefer raw strings for open-ended text; prefer enums for a closed set of named choices.\n:::\n', 'equality-and-identity.md': '+++\nslug = "equality-and-identity"\ntitle = "Equality and Identity"\nsection = "Data Model"\nsummary = "== compares values, while is compares object identity."\ndoc_path = "/reference/expressions.html#is-not"\n+++\n\nPython separates equality from identity. Equality asks whether two objects should be considered the same value, while identity asks whether two names point to the same object.\n\nThis distinction matters for mutable containers because two equal lists can still be independent objects. Mutating one should not imply mutating the other unless they share identity.\n\nThe `is` operator is best reserved for identity checks against singletons such as `None`. For ordinary values, `==` is the comparison readers expect.\n\n:::program\n```python\nleft = [1, 2, 3]\nright = [1, 2, 3]\nprint(left == right)\nprint(left is right)\n\nsame = left\nsame.append(4)\nprint(left)\nprint(same is left)\n\nvalue = None\nprint(value is None)\n\nsmall_a = 100\nsmall_b = 100\nprint(small_a is small_b)\n\nbig_a = int("1000")\nbig_b = int("1000")\nprint(big_a is big_b)\nprint(big_a == big_b)\n```\n:::\n\n:::cell\nEqual containers can be different objects. `==` compares list contents, while `is` checks whether both names refer to the same list object.\n\n```python\nleft = [1, 2, 3]\nright = [1, 2, 3]\nprint(left == right)\nprint(left is right)\n```\n\n```output\nTrue\nFalse\n```\n:::\n\n:::cell\nIdentity matters when objects are mutable. `same` is another name for `left`, so mutating through one name changes the object seen through the other.\n\n```python\nsame = left\nsame.append(4)\nprint(left)\nprint(same is left)\n```\n\n```output\n[1, 2, 3, 4]\nTrue\n```\n:::\n\n:::cell\nUse `is` for singleton identity checks such as `None`. This asks whether the value is the one special `None` object.\n\n```python\nvalue = None\nprint(value is None)\n```\n\n```output\nTrue\n```\n:::\n\n:::cell\n`is` for integers is unreliable because CPython caches small integers (roughly `-5` to `256`) but not larger ones. Two equal large integers can be different objects. Use `==` for value comparisons; reserve `is` for singletons.\n\n```python\nsmall_a = 100\nsmall_b = 100\nprint(small_a is small_b)\n\nbig_a = int("1000")\nbig_b = int("1000")\nprint(big_a is big_b)\nprint(big_a == big_b)\n```\n\n```output\nTrue\nFalse\nTrue\n```\n:::\n\n:::note\n- Use `==` for ordinary value comparisons.\n- Use `is` primarily for identity checks against singletons such as `None`.\n- Equal mutable containers can still be independent objects.\n- Never use `is` to compare numbers; CPython\'s small-integer cache makes the result an implementation detail.\n:::\n', 'exception-chaining.md': '+++\nslug = "exception-chaining"\ntitle = "Exception Chaining"\nsection = "Errors"\nsummary = "raise from preserves the original cause when translating exceptions."\ndoc_path = "/tutorial/errors.html#exception-chaining"\nsee_also = [\n "exceptions",\n "custom-exceptions",\n "assertions",\n]\n+++\n\nException chaining connects a higher-level error to the lower-level exception that caused it. The syntax is `raise NewError(...) from error`.\n\nUse chaining when translating implementation details into a domain-specific error while preserving the original cause for debugging.\n\nThis is different from hiding the original exception. The caller can catch the domain error, and tooling can still inspect `__cause__`.\n\n:::program\n```python\nclass ConfigError(Exception):\n pass\n\n\ndef read_port(text):\n try:\n return int(text)\n except ValueError as error:\n raise ConfigError("port must be a number") from error\n\nprint(ConfigError.__name__)\n\ntry:\n read_port("abc")\nexcept ConfigError as error:\n print(error)\n print(type(error.__cause__).__name__)\n```\n:::\n\n:::cell\nCatch the low-level exception where it happens, then raise a domain-specific exception from it.\n\n```python\nclass ConfigError(Exception):\n pass\n\n\ndef read_port(text):\n try:\n return int(text)\n except ValueError as error:\n raise ConfigError("port must be a number") from error\n\nprint(ConfigError.__name__)\n```\n\n```output\nConfigError\n```\n:::\n\n:::cell\nThe caller handles the domain error. The original `ValueError` remains available as `__cause__`.\n\n```python\ntry:\n read_port("abc")\nexcept ConfigError as error:\n print(error)\n print(type(error.__cause__).__name__)\n```\n\n```output\nport must be a number\nValueError\n```\n:::\n\n:::note\n- Use `raise ... from error` when translating exceptions across a boundary.\n- The new exception\'s `__cause__` points to the original exception.\n- Chaining keeps user-facing errors clear without losing debugging context.\n:::\n', 'exception-groups.md': '+++\nslug = "exception-groups"\ntitle = "Exception Groups"\nsection = "Errors"\nsummary = "except* handles matching exceptions inside an ExceptionGroup."\ndoc_path = "/tutorial/errors.html#raising-and-handling-multiple-unrelated-exceptions"\nsee_also = [\n "exceptions",\n "exception-chaining",\n "async-await",\n]\n+++\n\n`ExceptionGroup` represents several unrelated exceptions raised together. `except*` exists for code that may receive multiple failures at once, especially concurrent work.\n\nUse ordinary `except` for one exception. Use `except*` only when the value being handled is an exception group and each matching subgroup needs its own handling.\n\nEach `except*` clause receives a smaller exception group containing the matching exceptions.\n\n:::program\n```python\nerrors = ExceptionGroup(\n "batch failed",\n [ValueError("bad port"), TypeError("bad mode")],\n)\nprint(len(errors.exceptions))\n\ntry:\n raise errors\nexcept* ValueError as group:\n print(type(group).__name__)\n print(group.exceptions[0])\nexcept* TypeError as group:\n print(group.exceptions[0])\n```\n:::\n\n:::cell\nAn exception group bundles several exception objects. This is different from an ordinary exception because more than one failure is present.\n\n```python\nerrors = ExceptionGroup(\n "batch failed",\n [ValueError("bad port"), TypeError("bad mode")],\n)\nprint(len(errors.exceptions))\n```\n\n```output\n2\n```\n:::\n\n:::cell\n`except*` handles matching members of the group. The `ValueError` handler sees the value error, and the `TypeError` handler sees the type error.\n\n```python\ntry:\n raise errors\nexcept* ValueError as group:\n print(type(group).__name__)\n print(group.exceptions[0])\nexcept* TypeError as group:\n print(group.exceptions[0])\n```\n\n```output\nExceptionGroup\nbad port\nbad mode\n```\n:::\n\n:::note\n- `except*` is for `ExceptionGroup`, not ordinary single exceptions.\n- Each `except*` clause handles matching members of the group.\n- Exception groups often appear around concurrent work.\n:::\n', 'exceptions.md': '+++\nslug = "exceptions"\ntitle = "Exceptions"\nsection = "Errors"\nsummary = "Use try, except, else, and finally to separate success, recovery, and cleanup."\ndoc_path = "/tutorial/errors.html"\n+++\n\nExceptions represent errors or unusual conditions that interrupt normal control flow. `try` marks the operation that may fail, and `except` handles a specific failure where recovery makes sense.\n\nKeep the successful path separate from the recovery path. `else` runs only when no exception was raised, while `finally` runs either way for cleanup or bookkeeping.\n\nUse exceptions when an operation cannot produce a valid result. Prefer ordinary conditionals for expected branches that are not errors.\n\nCatch specific exceptions whenever possible. A broad catch can hide programming mistakes, while a targeted `ValueError` handler documents exactly what failure is expected.\n\n:::program\n```python\ndef parse_int(text):\n return int(text)\n\nfor text in ["42", "python"]:\n try:\n number = parse_int(text)\n except ValueError:\n print(f"{text}: invalid")\n else:\n print(f"{text}: {number}")\n finally:\n print(f"checked {text}")\n\n\ndef safe_parse_broken(text):\n try:\n return int(text)\n except Exception:\n return None\n\ndef safe_parse_fixed(text):\n try:\n return int(text)\n except ValueError:\n return None\n\nprint(safe_parse_broken("42"))\nprint(safe_parse_fixed("42"))\n```\n:::\n\n:::cell\nWhen no exception is raised, the `else` block runs. Keeping success in `else` makes the `try` block contain only the operation that might fail.\n\n```python\ndef parse_int(text):\n return int(text)\n\ntext = "42"\ntry:\n number = parse_int(text)\nexcept ValueError:\n print(f"{text}: invalid")\nelse:\n print(f"{text}: {number}")\nfinally:\n print(f"checked {text}")\n```\n\n```output\n42: 42\nchecked 42\n```\n:::\n\n:::cell\nWhen parsing fails, `int()` raises `ValueError`. Catching that specific exception makes the expected recovery path explicit.\n\n```python\ntext = "python"\ntry:\n number = parse_int(text)\nexcept ValueError:\n print(f"{text}: invalid")\nelse:\n print(f"{text}: {number}")\nfinally:\n print(f"checked {text}")\n```\n\n```output\npython: invalid\nchecked python\n```\n:::\n\n:::cell\nBare `except:` and broad `except Exception:` swallow far more than the failure you meant to handle, including `KeyboardInterrupt` (bare) and most programming bugs (broad). Catch the specific class — `ValueError` here — so unexpected failures still surface.\n\n```python\ndef safe_parse_broken(text):\n try:\n return int(text)\n except Exception:\n return None\n\ndef safe_parse_fixed(text):\n try:\n return int(text)\n except ValueError:\n return None\n\nprint(safe_parse_broken("42"))\nprint(safe_parse_fixed("42"))\n```\n\n```output\n42\n42\n```\n:::\n\n:::note\n- Catch the most specific exception you can.\n- `else` is for success code that should run only if the `try` block did not fail.\n- `finally` runs whether the operation succeeded or failed.\n- Avoid bare `except:` and broad `except Exception:` — they hide bugs and absorb signals like `KeyboardInterrupt`.\n:::\n', 'for-loops.md': '+++\nslug = "for-loops"\ntitle = "For Loops"\nsection = "Control Flow"\nsummary = "for iterates over any iterable object."\ndoc_path = "/tutorial/controlflow.html#for-statements"\n+++\n\nPython for loops iterate over values from an iterable. This is different from languages where for primarily means incrementing a numeric counter.\n\nrange() is itself an iterable that produces numbers lazily. Use it when you need a sequence of integers, but prefer direct iteration when you already have a collection.\n\nBlocks are defined by indentation. range(3) yields 0, 1, and 2.\n\n:::program\n```python\nfor name in ["Ada", "Grace", "Guido"]:\n print(name)\n\nfor number in range(3):\n print(number)\n```\n:::\n\n:::cell\nPython for loops iterate over values from an iterable. This is different from languages where for primarily means incrementing a numeric counter.\n\n```python\nfor name in ["Ada", "Grace", "Guido"]:\n print(name)\n```\n\n```output\nAda\nGrace\nGuido\n```\n:::\n\n:::cell\nrange() is itself an iterable that produces numbers lazily. Use it when you need a sequence of integers, but prefer direct iteration when you already have a collection.\n\n```python\nfor number in range(3):\n print(number)\n```\n\n```output\n0\n1\n2\n```\n:::\n\n:::note\n- Blocks are defined by indentation.\n- range(3) yields 0, 1, and 2.\n:::\n', 'functions.md': '+++\nslug = "functions"\ntitle = "Functions"\nsection = "Functions"\nsummary = "Use def to name reusable behavior and return results."\ndoc_path = "/tutorial/controlflow.html#defining-functions"\n+++\n\nFunctions package behavior behind a name. `def` creates a function object that can accept arguments, compute values, and return a result.\n\nDefault arguments make common calls short, and keyword arguments make call sites easier to read. A function that reaches the end without `return` produces `None`.\n\nUse functions when a calculation has a useful name, when code repeats, or when a piece of behavior should be tested independently.\n\n:::program\n```python\ndef greet(name):\n return f"Hello, {name}."\n\nprint(greet("Python"))\n\n\ndef format_total(amount, currency="USD"):\n return f"{amount} {currency}"\n\nprint(format_total(10))\nprint(format_total(10, currency="EUR"))\n\n\ndef log(message):\n print(f"log: {message}")\n\nresult = log("saved")\nprint(result)\n\n\ndef append_broken(item, items=[]):\n items.append(item)\n return items\n\nprint(append_broken("a"))\nprint(append_broken("b"))\n\n\ndef append_fixed(item, items=None):\n if items is None:\n items = []\n items.append(item)\n return items\n\nprint(append_fixed("a"))\nprint(append_fixed("b"))\n```\n:::\n\n:::cell\n`return` sends a value back to the caller. The caller can print it, store it, or pass it to another function.\n\n```python\ndef greet(name):\n return f"Hello, {name}."\n\nprint(greet("Python"))\n```\n\n```output\nHello, Python.\n```\n:::\n\n:::cell\nDefault arguments provide common values. Keyword arguments make it clear which option is being overridden.\n\n```python\ndef format_total(amount, currency="USD"):\n return f"{amount} {currency}"\n\nprint(format_total(10))\nprint(format_total(10, currency="EUR"))\n```\n\n```output\n10 USD\n10 EUR\n```\n:::\n\n:::cell\nA function without an explicit `return` returns `None`. That makes side-effect-only functions easy to distinguish from value-producing ones.\n\n```python\ndef log(message):\n print(f"log: {message}")\n\nresult = log("saved")\nprint(result)\n```\n\n```output\nlog: saved\nNone\n```\n:::\n\n:::cell\nMutable default arguments are evaluated once when the function is defined, not on each call. The same list is shared across calls, so successive calls see each other\'s mutations. Use `None` as the sentinel and create a fresh container inside the body.\n\n```python\ndef append_broken(item, items=[]):\n items.append(item)\n return items\n\nprint(append_broken("a"))\nprint(append_broken("b"))\n\n\ndef append_fixed(item, items=None):\n if items is None:\n items = []\n items.append(item)\n return items\n\nprint(append_fixed("a"))\nprint(append_fixed("b"))\n```\n\n```output\n[\'a\']\n[\'a\', \'b\']\n[\'a\']\n[\'b\']\n```\n:::\n\n:::note\n- Use `return` for values the caller should receive.\n- Defaults keep common calls concise.\n- Keyword arguments make options readable at the call site.\n- Never use a mutable value as a default argument; use `None` and build the container inside the function body.\n:::\n', 'generator-expressions.md': '+++\nslug = "generator-expressions"\ntitle = "Generator Expressions"\nsection = "Iteration"\nsummary = "Generator expressions use comprehension-like syntax to stream values lazily."\ndoc_path = "/tutorial/classes.html#generator-expressions"\n+++\n\nGenerator expressions look like list comprehensions with parentheses, but they produce an iterator instead of building a concrete collection immediately.\n\nUse them when a consumer such as `sum()`, `any()`, or a `for` loop can use values one at a time. This keeps the transformation close to the consumer and avoids storing intermediate lists.\n\nLike other iterators, a generator expression is consumed as values are requested. Create a new generator expression when you need another pass.\n\n:::program\n```python\nnumbers = [1, 2, 3, 4]\nlist_squares = [number * number for number in numbers]\nprint(list_squares)\n\nstream_squares = (number * number for number in numbers)\nprint(next(stream_squares))\nprint(next(stream_squares))\nprint(list(stream_squares))\n\nprint(sum(number * number for number in numbers))\n```\n:::\n\n:::cell\nA list comprehension is eager: it builds a list immediately. That is useful when you need to store or reuse the results.\n\n```python\nnumbers = [1, 2, 3, 4]\nlist_squares = [number * number for number in numbers]\nprint(list_squares)\n```\n\n```output\n[1, 4, 9, 16]\n```\n:::\n\n:::cell\nA generator expression is lazy: it creates an iterator that produces values as they are consumed. After two `next()` calls, only the remaining squares are left.\n\n```python\nstream_squares = (number * number for number in numbers)\nprint(next(stream_squares))\nprint(next(stream_squares))\nprint(list(stream_squares))\n```\n\n```output\n1\n4\n[9, 16]\n```\n:::\n\n:::cell\nGenerator expressions are common inside reducing functions. When a generator expression is the only argument, the extra parentheses can be omitted.\n\n```python\nprint(sum(number * number for number in numbers))\n```\n\n```output\n30\n```\n:::\n\n:::note\n- List, dict, and set comprehensions build concrete collections.\n- Generator expressions produce one-pass iterators.\n- Use generator expressions when the consumer can process values one at a time.\n:::\n', 'generators.md': '+++\nslug = "generators"\ntitle = "Generators"\nsection = "Iteration"\nsummary = "yield creates an iterator that produces values on demand."\ndoc_path = "/tutorial/classes.html#generators"\nsee_also = [\n "iterators",\n "iterator-vs-iterable",\n "generator-expressions",\n]\n+++\n\nA generator function is a convenient way to write your own iterator. `yield` produces one value, pauses the function, and resumes when the next value is requested.\n\nGenerators are useful for pipelines, large inputs, and infinite sequences because they avoid building an entire collection in memory.\n\nUse `next()` to request one value manually, or loop over the generator to consume values until it is exhausted.\n\n:::program\n```python\ndef countdown(n):\n while n > 0:\n yield n\n n -= 1\n\nnumbers = countdown(3)\nprint(next(numbers))\nprint(next(numbers))\n\nfor value in countdown(3):\n print(value)\n\ndef countdown_eager(n):\n result = []\n while n > 0:\n result.append(n)\n n -= 1\n return result\n\nvalues = countdown_eager(3)\nprint(values)\nprint(values)\n\nstream = countdown(3)\nprint(list(stream))\nprint(list(stream))\n\nclass Countdown:\n def __init__(self, n):\n self.n = n\n\n def __iter__(self):\n return self\n\n def __next__(self):\n if self.n <= 0:\n raise StopIteration\n value = self.n\n self.n -= 1\n return value\n\nprint(list(Countdown(3)))\n```\n:::\n\n:::cell\nCalling a generator function returns an iterator. `next()` asks for one value and resumes the function until the next `yield`.\n\n```python\ndef countdown(n):\n while n > 0:\n yield n\n n -= 1\n\nnumbers = countdown(3)\nprint(next(numbers))\nprint(next(numbers))\n```\n\n```output\n3\n2\n```\n:::\n\n:::cell\nA `for` loop repeatedly calls `next()` for you. The loop stops when the generator is exhausted.\n\n```python\nfor value in countdown(3):\n print(value)\n```\n\n```output\n3\n2\n1\n```\n:::\n\n:::cell\n`return` builds the entire result before handing it back; `yield` produces values on demand. The list keeps its values for repeated use, while the generator is exhausted after one pass.\n\n```python\ndef countdown_eager(n):\n result = []\n while n > 0:\n result.append(n)\n n -= 1\n return result\n\nvalues = countdown_eager(3)\nprint(values)\nprint(values)\n\nstream = countdown(3)\nprint(list(stream))\nprint(list(stream))\n```\n\n```output\n[3, 2, 1]\n[3, 2, 1]\n[3, 2, 1]\n[]\n```\n:::\n\n:::cell\nEvery generator is an iterator. The same countdown written by hand needs `__iter__` and `__next__` and an explicit `StopIteration`. The generator function expresses the same protocol with one `yield`.\n\n```python\nclass Countdown:\n def __init__(self, n):\n self.n = n\n\n def __iter__(self):\n return self\n\n def __next__(self):\n if self.n <= 0:\n raise StopIteration\n value = self.n\n self.n -= 1\n return value\n\nprint(list(Countdown(3)))\n```\n\n```output\n[3, 2, 1]\n```\n:::\n\n:::note\n- Generator functions are a concise way to create custom iterators; every generator is an iterator.\n- `yield` defers work and streams values; `return` produces the whole result up front.\n- A generator is consumed as you iterate over it.\n- Prefer a list when you need to reuse stored results; prefer a generator when values can be streamed once.\n:::\n', 'generics-and-typevar.md': '+++\nslug = "generics-and-typevar"\ntitle = "Generics and TypeVar"\nsection = "Types"\nsummary = "Generics preserve type information across reusable functions and classes."\ndoc_path = "/library/typing.html#generics"\nsee_also = [\n "type-hints",\n "collections-module",\n "casts-and-any",\n]\n+++\n\nGenerics connect types across an API. A plain function that returns `object` loses information; a generic function can say that the returned value has the same type as the input element.\n\nA `TypeVar` stands for a type chosen by the caller. In `list[T] -> T`, the same `T` says that a list of strings produces a string and a list of integers produces an integer.\n\nUse generics when a function or class is reusable but still preserves a relationship between input and output types.\n\n:::program\n```python\nfrom typing import TypeVar\n\nT = TypeVar("T")\n\n\ndef first(items: list[T]) -> T:\n return items[0]\n\n\ndef pair(left: T, right: T) -> tuple[T, T]:\n return (left, right)\n\nprint(first([1, 2, 3]))\nprint(first(["Ada", "Grace"]))\nprint(pair("x", "y"))\nprint(T.__name__)\n```\n:::\n\n:::cell\nA `TypeVar` stands for a type chosen by the caller. The return type follows the list element type.\n\n```python\nfrom typing import TypeVar\n\nT = TypeVar("T")\n\n\ndef first(items: list[T]) -> T:\n return items[0]\n\nprint(first([1, 2, 3]))\nprint(first(["Ada", "Grace"]))\n```\n\n```output\n1\nAda\n```\n:::\n\n:::cell\nReusing the same `TypeVar` expresses a relationship between parameters and results.\n\n```python\ndef pair(left: T, right: T) -> tuple[T, T]:\n return (left, right)\n\nprint(pair("x", "y"))\n```\n\n```output\n(\'x\', \'y\')\n```\n:::\n\n:::cell\n`TypeVar` is visible at runtime, but the relationship is mainly for type checkers.\n\n```python\nprint(T.__name__)\nprint(first.__annotations__)\n```\n\n```output\nT\n{\'items\': list[~T], \'return\': ~T}\n```\n:::\n\n:::note\n- A `TypeVar` stands for a type chosen by the caller.\n- Generic functions avoid losing information to `object` or `Any`.\n- Use generics when input and output types are connected.\n:::\n', 'guard-clauses.md': '+++\nslug = "guard-clauses"\ntitle = "Guard Clauses"\nsection = "Control Flow"\nsummary = "Guard clauses handle exceptional cases early so the main path stays flat."\ndoc_path = "/tutorial/controlflow.html#if-statements"\n+++\n\nGuard clauses handle exceptional cases early so the main path stays flat. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.\n\nUse it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.\n\nThe example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.\n\n:::program\n```python\ndef price_after_discount(price, percent):\n if price < 0:\n return "invalid price"\n if not 0 <= percent <= 100:\n return "invalid discount"\n\n discount = price * percent / 100\n return round(price - discount, 2)\n\nprint(price_after_discount(100, 15))\nprint(price_after_discount(-5, 10))\nprint(price_after_discount(100, 120))\n```\n:::\n\n:::cell\nReturn early when inputs cannot be handled.\n\n```python\ndef price_after_discount(price, percent):\n if price < 0:\n return "invalid price"\n if not 0 <= percent <= 100:\n return "invalid discount"\n\n discount = price * percent / 100\n return round(price - discount, 2)\n\nprint(price_after_discount(100, 15))\nprint(price_after_discount(-5, 10))\nprint(price_after_discount(100, 120))\n```\n\n```output\n85.0\ninvalid price\ninvalid discount\n```\n:::\n\n:::note\n- Return early when inputs cannot be handled.\n- After the guards, the remaining code can read as the normal path.\n- Guard clauses are a style choice, not new syntax.\n:::\n', 'hello-world.md': '+++\nslug = "hello-world"\ntitle = "Hello World"\nsection = "Basics"\nsummary = "The first Python program prints a line of text."\ndoc_path = "/tutorial/introduction.html"\n+++\n\nEvery Python program starts by executing statements from top to bottom. Calling `print()` is the smallest useful program because it shows how Python evaluates an expression and sends text to standard output.\n\nStrings are ordinary values, so the message passed to `print()` can be changed, stored in a variable, or produced by a function. This example keeps the first program intentionally small.\n\n`print()` writes text followed by a newline. Strings can be delimited with single or double quotes.\n\n:::program\n```python\nprint("hello world")\n```\n:::\n\n:::cell\nEvery Python program starts by executing statements from top to bottom. Calling `print()` is the smallest useful program because it shows how Python evaluates an expression and sends text to standard output.\n\n```python\nprint("hello world")\n```\n\n```output\nhello world\n```\n:::\n\n:::note\n- `print()` writes text followed by a newline.\n- Strings can be delimited with single or double quotes.\n:::\n', 'import-aliases.md': '+++\nslug = "import-aliases"\ntitle = "Import Aliases"\nsection = "Modules"\nsummary = "as gives imported modules or names a local alias."\ndoc_path = "/reference/simple_stmts.html#the-import-statement"\nsee_also = [\n "modules",\n "functions",\n]\n+++\n\n`as` gives an imported module or imported name a local alias. Use it when a conventional short name improves readability or when two imports would otherwise collide.\n\nThe alternative is a plain import, which is usually better when the module name is already clear. Avoid aliases that make readers guess where a name came from.\n\nAvoid star imports in examples and production modules because they hide dependencies and blur the boundary between modules.\n\n:::program\n```python\nimport statistics as stats\nfrom math import sqrt as square_root\n\nscores = [8, 10, 9]\nprint(stats.mean(scores))\nprint(stats.__name__)\n\nprint(square_root(81))\nprint(square_root.__name__)\n```\n:::\n\n:::cell\nA module alias keeps the namespace but changes the local name. Here `stats` is shorter, but readers can still see that `mean` belongs to the statistics module.\n\n```python\nimport statistics as stats\n\nscores = [8, 10, 9]\nprint(stats.mean(scores))\nprint(stats.__name__)\n```\n\n```output\n9\nstatistics\n```\n:::\n\n:::cell\nA name imported with `from` can also be aliased. Use this when the local name explains the role better than the original name.\n\n```python\nfrom math import sqrt as square_root\n\nprint(square_root(81))\nprint(square_root.__name__)\n```\n\n```output\n9.0\nsqrt\n```\n:::\n\n:::note\n- `import module as alias` keeps module-style access under a shorter or clearer name.\n- `from module import name as alias` imports one name under a local alias.\n- Prefer plain imports unless an alias improves clarity or follows a strong convention.\n- Avoid `from module import *` because it makes dependencies harder to see.\n:::\n', 'inheritance-and-super.md': '+++\nslug = "inheritance-and-super"\ntitle = "Inheritance and Super"\nsection = "Classes"\nsummary = "Inheritance reuses behavior, and super delegates to a parent implementation."\ndoc_path = "/tutorial/classes.html#inheritance"\nsee_also = [\n "classes",\n "abstract-base-classes",\n "classmethods-and-staticmethods",\n "special-methods",\n]\n+++\n\nInheritance lets one class specialize another class. The child class gets parent behavior and can add or override methods.\n\nUse `super()` when the child method should extend the parent implementation instead of replacing it entirely.\n\nPrefer composition when objects merely collaborate. Inheritance is best when the child really is a specialized version of the parent.\n\n:::program\n```python\nclass Animal:\n def __init__(self, name):\n self.name = name\n\n def speak(self):\n return f"{self.name} makes a sound"\n\nclass Dog(Animal):\n def speak(self):\n base = super().speak()\n return f"{base}; {self.name} barks"\n\npet = Dog("Nina")\nprint(pet.name)\nprint(pet.speak())\nprint(isinstance(pet, Animal))\n```\n:::\n\n:::cell\nA child class names its parent in parentheses. `Dog` instances get the `Animal.__init__` method because `Dog` does not define its own initializer.\n\n```python\nclass Animal:\n def __init__(self, name):\n self.name = name\n\n def speak(self):\n return f"{self.name} makes a sound"\n\nclass Dog(Animal):\n def speak(self):\n base = super().speak()\n return f"{base}; {self.name} barks"\n\npet = Dog("Nina")\nprint(pet.name)\n```\n\n```output\nNina\n```\n:::\n\n:::cell\n`super()` delegates to the parent implementation. The child method can reuse the parent result and then add specialized behavior.\n\n```python\nprint(pet.speak())\nprint(isinstance(pet, Animal))\n```\n\n```output\nNina makes a sound; Nina barks\nTrue\n```\n:::\n\n:::note\n- Inheritance models an “is a specialized kind of” relationship.\n- `super()` calls the next implementation in the method resolution order.\n- Prefer composition when an object only needs to use another object.\n:::\n', 'iterating-over-iterables.md': '+++\nslug = "iterating-over-iterables"\ntitle = "Iterating over Iterables"\nsection = "Iteration"\nsummary = "for loops consume values from any iterable object."\ndoc_path = "/tutorial/controlflow.html#for-statements"\nsee_also = [\n "iterators",\n "iterator-vs-iterable",\n "for-loops",\n]\n+++\n\nPython\'s `for` statement consumes values from any iterable object: lists, strings, dictionaries, ranges, generators, files, and many standard-library helpers.\n\nThis makes iteration a value-stream protocol rather than a special case for arrays. The producer decides how values are made, and the loop consumes them one at a time.\n\nUse `enumerate()` when you need positions and values together, and `dict.items()` when you need keys and values. These helpers express intent better than manual indexing.\n\n:::program\n```python\nnames = ["Ada", "Grace", "Guido"]\n\nfor name in names:\n print(name)\n\nfor index, name in enumerate(names):\n print(index, name)\n\nscores = {"Ada": 10, "Grace": 9}\nfor name, score in scores.items():\n print(name, score)\n```\n:::\n\n:::cell\nStart with an ordinary list. A list stores values, and a `for` loop asks it for one value at a time.\n\nWhen you only need the values, iterate over the collection directly. There is no index variable because the loop body does not need one.\n\n```python\nnames = ["Ada", "Grace", "Guido"]\n\nfor name in names:\n print(name)\n```\n\n```output\nAda\nGrace\nGuido\n```\n:::\n\n:::cell\nWhen you need both a position and a value, use `enumerate()`. It produces index/value pairs without manual indexing.\n\n```python\nfor index, name in enumerate(names):\n print(index, name)\n```\n\n```output\n0 Ada\n1 Grace\n2 Guido\n```\n:::\n\n:::cell\nDictionaries are iterable too, but `dict.items()` is the clearest way to say that the loop needs keys and values together.\n\n```python\nscores = {"Ada": 10, "Grace": 9}\nfor name, score in scores.items():\n print(name, score)\n```\n\n```output\nAda 10\nGrace 9\n```\n:::\n\n:::note\n- A `for` loop consumes values from an iterable.\n- Different producers can feed the same loop protocol.\n- Prefer `enumerate()` over `range(len(...))` when you need an index.\n:::\n', 'iterator-vs-iterable.md': '+++\nslug = "iterator-vs-iterable"\ntitle = "Iterator vs Iterable"\nsection = "Iteration"\nsummary = "Iterables produce fresh iterators; iterators are one-pass."\ndoc_path = "/glossary.html#term-iterable"\nsee_also = [\n "iterators",\n "iterating-over-iterables",\n "generators",\n]\n+++\n\nAn iterable can produce values when asked. An iterator is the object that remembers where the production currently is. The distinction matters because iterables can be traversed many times, while many iterators can be traversed only once.\n\n`iter(iterable)` returns a fresh iterator each call. `iter(iterator)` returns the iterator itself. That self-iteration property is how `for` loops can accept either kind, and it is also why a function that loops over its argument twice silently breaks when called with a generator instead of a list.\n\nThe takeaway for API design: receive iterables when the caller may want a second pass, and materialize once at the boundary if you must.\n\n:::program\n```python\nnames = ["Ada", "Grace"]\n\nprint(list(names))\nprint(list(names))\n\nstream = iter(names)\nprint(list(stream))\nprint(list(stream))\n\nfirst = iter(names)\nsecond = iter(names)\nprint(first is second)\nprint(iter(first) is first)\n\ndef total_and_count(numbers):\n total = sum(numbers)\n count = sum(1 for _ in numbers)\n return total, count\n\ndef values():\n yield from [10, 9, 8]\n\nprint(total_and_count([10, 9, 8]))\nprint(total_and_count(values()))\n\ndef total_and_count_safe(numbers):\n items = list(numbers)\n return sum(items), len(items)\n\nprint(total_and_count_safe(values()))\n```\n:::\n\n:::cell\nA list is iterable. Each `for` loop or `list()` call asks the list for a fresh iterator under the hood, so the same data can be traversed many times.\n\n```python\nnames = ["Ada", "Grace"]\nprint(list(names))\nprint(list(names))\n```\n\n```output\n[\'Ada\', \'Grace\']\n[\'Ada\', \'Grace\']\n```\n:::\n\n:::cell\nAn iterator is one-pass. Calling `iter()` returns a position-tracking object; once it has been exhausted, it stays exhausted.\n\n```python\nstream = iter(names)\nprint(list(stream))\nprint(list(stream))\n```\n\n```output\n[\'Ada\', \'Grace\']\n[]\n```\n:::\n\n:::cell\nCalling `iter()` on an iterable returns a brand-new iterator each time. Calling `iter()` on an iterator returns the same object — that is the rule that lets a `for` loop accept either kind.\n\n```python\nfirst = iter(names)\nsecond = iter(names)\nprint(first is second)\nprint(iter(first) is first)\n```\n\n```output\nFalse\nTrue\n```\n:::\n\n:::cell\nThe distinction shows up at API boundaries. A function that loops over its argument twice works for an iterable but silently produces wrong answers for an iterator, because the second pass finds the iterator already exhausted. Materialize once at the boundary when both passes matter.\n\n```python\ndef total_and_count(numbers):\n total = sum(numbers)\n count = sum(1 for _ in numbers)\n return total, count\n\ndef values():\n yield from [10, 9, 8]\n\nprint(total_and_count([10, 9, 8]))\nprint(total_and_count(values()))\n\ndef total_and_count_safe(numbers):\n items = list(numbers)\n return sum(items), len(items)\n\nprint(total_and_count_safe(values()))\n```\n\n```output\n(27, 3)\n(27, 0)\n(27, 3)\n```\n:::\n\n:::note\n- An iterable produces an iterator each time `iter()` is called on it; an iterator produces values until it is exhausted.\n- `iter(iterable)` returns a fresh iterator; `iter(iterator)` returns the same iterator.\n- Functions that traverse their input more than once must accept an iterable or materialize the input at the boundary.\n:::\n', 'iterators.md': '+++\nslug = "iterators"\ntitle = "Iterators"\nsection = "Iteration"\nsummary = "iter and next expose the protocol behind for loops."\ndoc_path = "/library/stdtypes.html#iterator-types"\nsee_also = [\n "iterating-over-iterables",\n "iterator-vs-iterable",\n "generators",\n]\n+++\n\nAn iterable is an object that can produce values for a loop. An iterator is the object that remembers where that production currently is.\n\n`iter()` asks an iterable for an iterator, and `next()` consumes one value from that iterator. A `for` loop performs those steps for you until the iterator is exhausted.\n\nThis is the core value-stream protocol in Python: one object produces values, another piece of code consumes them, and many streams are one-pass.\n\n:::program\n```python\nnames = ["Ada", "Grace", "Guido"]\niterator = iter(names)\nprint(next(iterator))\nprint(next(iterator))\n\nfor name in iterator:\n print(name)\n\nagain = iter(names)\nprint(next(again))\n```\n:::\n\n:::cell\n`iter()` asks an iterable for an iterator. `next()` consumes one value and advances the iterator\'s position.\n\n```python\nnames = ["Ada", "Grace", "Guido"]\niterator = iter(names)\nprint(next(iterator))\nprint(next(iterator))\n```\n\n```output\nAda\nGrace\n```\n:::\n\n:::cell\nA `for` loop consumes the same iterator protocol. Because two values were already consumed, the loop sees only the remaining value.\n\n```python\nfor name in iterator:\n print(name)\n```\n\n```output\nGuido\n```\n:::\n\n:::cell\nThe list itself is reusable. Asking it for a fresh iterator starts a new pass over the same stored values.\n\n```python\nagain = iter(names)\nprint(next(again))\n```\n\n```output\nAda\n```\n:::\n\n:::note\n- Iterables produce iterators; iterators produce values.\n- `next()` consumes one value from an iterator.\n- Many iterators are one-pass even when the original collection is reusable.\n:::\n', 'itertools.md': '+++\nslug = "itertools"\ntitle = "Itertools"\nsection = "Iteration"\nsummary = "itertools composes lazy iterator streams."\ndoc_path = "/library/itertools.html"\n+++\n\nThe `itertools` module contains tools for composing iterator streams: combining, slicing, grouping, and repeating values without changing the consumer protocol.\n\nMany `itertools` functions are lazy. They describe work to do later instead of building a list immediately, so helpers such as `islice()` are useful when taking a finite window.\n\nIterator pipelines let each step stay small: one object produces values, another transforms them, and a final consumer such as `list()` or a loop pulls values through the pipeline.\n\n:::program\n```python\nimport itertools\n\ncounter = itertools.count(10)\nprint(list(itertools.islice(counter, 3)))\n\npages = itertools.chain(["intro", "setup"], ["deploy"])\nprint(list(pages))\n\nscores = [7, 10, 8, 10]\nhigh_scores = itertools.compress(scores, [score >= 9 for score in scores])\nprint(list(high_scores))\n```\n:::\n\n:::cell\n`count()` can produce values forever, so `islice()` takes a finite window. Nothing is materialized until `list()` consumes the iterator.\n\n```python\nimport itertools\n\ncounter = itertools.count(10)\nprint(list(itertools.islice(counter, 3)))\n```\n\n```output\n[10, 11, 12]\n```\n:::\n\n:::cell\n`chain()` presents several iterables as one stream. This avoids building an intermediate list just to loop over combined inputs.\n\n```python\npages = itertools.chain(["intro", "setup"], ["deploy"])\nprint(list(pages))\n```\n\n```output\n[\'intro\', \'setup\', \'deploy\']\n```\n:::\n\n:::cell\nIterator helpers compose with ordinary Python expressions. `compress()` keeps items whose corresponding selector is true.\n\n```python\nscores = [7, 10, 8, 10]\nhigh_scores = itertools.compress(scores, [score >= 9 for score in scores])\nprint(list(high_scores))\n```\n\n```output\n[10, 10]\n```\n:::\n\n:::note\n- `itertools` composes producer and transformer streams.\n- Iterator pipelines avoid building intermediate lists.\n- Use `islice()` to take a finite piece from an infinite iterator.\n- Convert to a list only when you need concrete results.\n:::\n', 'json.md': '+++\nslug = "json"\ntitle = "JSON"\nsection = "Standard Library"\nsummary = "json encodes Python values as JSON text and decodes them back."\ndoc_path = "/library/json.html"\nsee_also = [\n "dicts",\n "typed-dicts",\n "strings",\n]\n+++\n\nThe `json` module converts between Python values and JSON text. Dictionaries, lists, strings, numbers, booleans, and `None` map naturally to JSON structures.\n\nUse `dumps()` when you need a string and `loads()` when you need Python objects back. Options such as `sort_keys=True` and `indent=2` control stable, readable output.\n\nJSON is a data format, not a way to preserve arbitrary Python objects. Encode simple data structures at service boundaries, and expect decode errors when the incoming text is not valid JSON.\n\n:::program\n```python\nimport json\n\npayload = {"language": "Python", "versions": [3, 13], "stable": True, "missing": None}\ntext = json.dumps(payload, sort_keys=True)\nprint(text)\n\npretty = json.dumps({"language": "Python", "stable": True}, indent=2, sort_keys=True)\nprint(pretty.splitlines()[0])\nprint(pretty.splitlines()[1])\n\ndecoded = json.loads(text)\nprint(decoded["language"])\nprint(decoded["missing"] is None)\n\ntry:\n json.loads("{bad json}")\nexcept json.JSONDecodeError as error:\n print(error.__class__.__name__)\n```\n:::\n\n:::cell\n`dumps()` encodes Python data as JSON text. `sort_keys=True` keeps dictionary keys in a stable order for reproducible output.\n\n```python\nimport json\n\npayload = {"language": "Python", "versions": [3, 13], "stable": True, "missing": None}\ntext = json.dumps(payload, sort_keys=True)\nprint(text)\n```\n\n```output\n{"language": "Python", "missing": null, "stable": true, "versions": [3, 13]}\n```\n:::\n\n:::cell\nFormatting options change the JSON text, not the Python value. `indent=2` is useful for human-readable output.\n\n```python\npretty = json.dumps({"language": "Python", "stable": True}, indent=2, sort_keys=True)\nprint(pretty.splitlines()[0])\nprint(pretty.splitlines()[1])\n```\n\n```output\n{\n "language": "Python",\n```\n:::\n\n:::cell\n`loads()` decodes JSON text back into Python values. JSON `null` becomes Python `None`.\n\n```python\ndecoded = json.loads(text)\nprint(decoded["language"])\nprint(decoded["missing"] is None)\n```\n\n```output\nPython\nTrue\n```\n:::\n\n:::cell\nInvalid JSON raises `JSONDecodeError`, so input boundaries should handle decode failures explicitly.\n\n```python\ntry:\n json.loads("{bad json}")\nexcept json.JSONDecodeError as error:\n print(error.__class__.__name__)\n```\n\n```output\nJSONDecodeError\n```\n:::\n\n:::note\n- `dumps()` returns a string; `loads()` accepts a string.\n- JSON `true`, `false`, and `null` become Python `True`, `False`, and `None`.\n- Use `sort_keys=True` when stable text output matters.\n- JSON only represents data shapes, not arbitrary Python objects or behavior.\n:::\n', 'keyword-only-arguments.md': '+++\nslug = "keyword-only-arguments"\ntitle = "Keyword-only Arguments"\nsection = "Functions"\nsummary = "Use * to require selected function arguments to be named."\ndoc_path = "/tutorial/controlflow.html#special-parameters"\n+++\n\nA bare `*` in a function signature marks the following parameters as keyword-only. Callers must name those arguments explicitly.\n\nKeyword-only arguments are useful for options such as timeouts, flags, and modes where positional calls would be ambiguous or easy to misread.\n\nThey let the required data stay positional while optional controls remain self-documenting at the call site.\n\n:::program\n```python\ndef connect(host, *, timeout=5, secure=True):\n scheme = "https" if secure else "http"\n print(f"{scheme}://{host} timeout={timeout}")\n\nconnect("example.com")\nconnect("example.com", timeout=10)\nconnect("localhost", secure=False)\n```\n:::\n\n:::cell\nParameters after `*` must be named. The default options still apply when the caller omits them.\n\n```python\ndef connect(host, *, timeout=5, secure=True):\n scheme = "https" if secure else "http"\n print(f"{scheme}://{host} timeout={timeout}")\n\nconnect("example.com")\n```\n\n```output\nhttps://example.com timeout=5\n```\n:::\n\n:::cell\nNaming the option makes the call site explicit. A reader does not have to remember which positional slot controls the timeout.\n\n```python\nconnect("example.com", timeout=10)\n```\n\n```output\nhttps://example.com timeout=10\n```\n:::\n\n:::cell\nFlags are especially good keyword-only arguments because a bare positional `False` is hard to interpret.\n\n```python\nconnect("localhost", secure=False)\n```\n\n```output\nhttp://localhost timeout=5\n```\n:::\n\n:::note\n- Put `*` before options that callers should name.\n- Keyword-only flags avoid mysterious positional `True` and `False` arguments.\n- Defaults work normally for keyword-only parameters.\n:::\n', 'lambdas.md': '+++\nslug = "lambdas"\ntitle = "Lambdas"\nsection = "Functions"\nsummary = "lambda creates small anonymous function expressions."\ndoc_path = "/tutorial/controlflow.html#lambda-expressions"\n+++\n\n`lambda` creates a small anonymous function expression. It is most useful when Python asks for a function and the behavior is short enough to read inline.\n\nA lambda can only contain one expression. Use `def` when the behavior deserves a name, needs statements, or would be easier to test separately.\n\nLambdas often appear as key functions, callbacks, and tiny adapters. Keep them simple enough that the call site remains clearer than a named helper.\n\n:::program\n```python\nadd_tax = lambda price: round(price * 1.08, 2)\nprint(add_tax(10))\n\nitems = [("notebook", 5), ("pen", 2), ("bag", 20)]\nby_price = sorted(items, key=lambda item: item[1])\nprint(by_price)\n\ndef price(item):\n return item[1]\n\nprint(sorted(items, key=price))\n```\n:::\n\n:::cell\nA lambda is a function expression. Assigning one to a name works, although `def` is usually clearer for reusable behavior.\n\n```python\nadd_tax = lambda price: round(price * 1.08, 2)\nprint(add_tax(10))\n```\n\n```output\n10.8\n```\n:::\n\n:::cell\nLambdas are most idiomatic when passed directly to another function. `sorted()` calls this key function once for each item.\n\n```python\nitems = [("notebook", 5), ("pen", 2), ("bag", 20)]\nby_price = sorted(items, key=lambda item: item[1])\nprint(by_price)\n```\n\n```output\n[(\'pen\', 2), (\'notebook\', 5), (\'bag\', 20)]\n```\n:::\n\n:::cell\nA named function is better when the behavior should be reused or explained. It produces the same sort key, but gives the operation a name.\n\n```python\ndef price(item):\n return item[1]\n\nprint(sorted(items, key=price))\n```\n\n```output\n[(\'pen\', 2), (\'notebook\', 5), (\'bag\', 20)]\n```\n:::\n\n:::note\n- Lambdas are expressions, not statements.\n- Prefer `def` for multi-step or reused behavior.\n- Lambdas are common as `key=` functions because the behavior is local to one call.\n:::\n', 'lists.md': '+++\nslug = "lists"\ntitle = "Lists"\nsection = "Collections"\nsummary = "Lists are ordered, mutable collections."\ndoc_path = "/tutorial/datastructures.html#more-on-lists"\n+++\n\nLists are Python\'s general-purpose mutable sequence type. Use them when order matters and the collection may grow, shrink, or be rearranged.\n\nIndexing reads individual positions. `0` is the first item, and negative indexes count backward from the end.\n\nMutation and copying matter: `append()` changes the list, while `sorted()` returns a new ordered list and leaves the original alone.\n\n:::program\n```python\nnumbers = [3, 1, 4]\nnumbers.append(1)\n\nprint(numbers)\nprint(numbers[0])\nprint(numbers[-1])\nprint(sorted(numbers))\nprint(numbers)\n```\n:::\n\n:::cell\nCreate a list with square brackets. Because lists are mutable, `append()` changes this same list object.\n\n```python\nnumbers = [3, 1, 4]\nnumbers.append(1)\n\nprint(numbers)\n```\n\n```output\n[3, 1, 4, 1]\n```\n:::\n\n:::cell\nUse indexes to read positions. Negative indexes are convenient for reading from the end.\n\n```python\nprint(numbers[0])\nprint(numbers[-1])\n```\n\n```output\n3\n1\n```\n:::\n\n:::cell\nUse `sorted()` when you want an ordered copy and still need the original order afterward.\n\n```python\nprint(sorted(numbers))\nprint(numbers)\n```\n\n```output\n[1, 1, 3, 4]\n[3, 1, 4, 1]\n```\n:::\n\n:::note\n- Lists are mutable sequences: methods such as `append()` change the list in place.\n- Negative indexes count from the end.\n- `sorted()` returns a new list; `list.sort()` sorts the existing list in place.\n:::\n', 'literal-and-final.md': '+++\nslug = "literal-and-final"\ntitle = "Literal and Final"\nsection = "Types"\nsummary = "Literal restricts values, while Final marks names that should not be rebound."\ndoc_path = "/library/typing.html#typing.Literal"\n+++\n\n`Literal` restricts a value to one of a small set of exact options, and `Final` tells the type checker that a name should not be rebound. Both are static promises that type checkers enforce; Python\'s runtime assignment rules still permit any value through if a program ignores the annotation.\n\nUse them when an annotation makes a constant or a small option set explicit at the API boundary. Prefer simpler neighboring tools when the extra machinery would hide the intent.\n\n`Literal` pairs naturally with type aliases and overloads when a function should accept only a known set of strings or numbers. `Final` is most useful for module-level constants and class attributes that the rest of the codebase should treat as immutable.\n\n:::program\n```python\nfrom typing import Final, Literal\n\nMode = Literal["read", "write"]\nDEFAULT_MODE: Final[Mode] = "read"\n\n\ndef open_label(mode: Mode) -> str:\n return f"opening for {mode}"\n\nprint(open_label(DEFAULT_MODE))\nprint(open_label("write"))\nprint(DEFAULT_MODE)\n```\n:::\n\n:::cell\n`Literal` describes a small set of exact allowed values.\n\n```python\nfrom typing import Final, Literal\n\nMode = Literal["read", "write"]\nDEFAULT_MODE: Final[Mode] = "read"\n\n\ndef open_label(mode: Mode) -> str:\n return f"opening for {mode}"\n\nprint(open_label(DEFAULT_MODE))\nprint(open_label("write"))\nprint(DEFAULT_MODE)\n```\n\n```output\nopening for read\nopening for write\nread\n```\n:::\n\n:::note\n- `Literal` describes a small set of exact allowed values.\n- `Final` tells type checkers that a name should not be rebound.\n- Both are static promises; ordinary runtime assignment rules still apply.\n:::\n', 'literals.md': '+++\nslug = "literals"\ntitle = "Literals"\nsection = "Basics"\nsummary = "Literals write values directly in Python source code."\ndoc_path = "/reference/lexical_analysis.html#literals"\nsee_also = [\n "values",\n "strings",\n "numbers",\n "string-formatting",\n]\n+++\n\nLiterals are source-code forms for values: numbers, text, bytes, containers, booleans, `None`, and a few specialized markers. They are how a program writes small values directly.\n\nThe literal form is only the beginning. Later examples explain each value family in depth: strings are Unicode text, bytes are binary data, lists and dicts are containers, and `None` represents intentional absence.\n\nUse literals when the value is small and local. Give repeated or meaningful values a name so the program explains why that value matters.\n\n:::program\n```python\nwhole = 42\nfraction = 3.5\ncomplex_number = 2 + 3j\nprint(whole, fraction, complex_number.imag)\n\nflags = 0xFF\nmask = 0b1010\nmillion = 1_000_000\nprint(flags, mask, million)\n\ntext = "python"\nraw_pattern = r"\\d+"\ndata = b"py"\nscore = 98\nformatted = f"score={score}"\nprint(text)\nprint(raw_pattern)\nprint(data)\nprint(formatted)\n\npoint = (2, 3)\nnames = ["Ada", "Grace"]\nscores = {"Ada": 98}\nunique = {"py", "go"}\nprint(point)\nprint(names[0])\nprint(scores["Ada"])\nprint(sorted(unique))\n\nprint(True, False, None)\nprint(...)\n\nprint(type({}).__name__)\nprint(type(set()).__name__)\nprint(type({1, 2}).__name__)\n```\n:::\n\n:::cell\nNumeric literals write numbers directly. Complex literals use `j` for the imaginary part.\n\n```python\nwhole = 42\nfraction = 3.5\ncomplex_number = 2 + 3j\nprint(whole, fraction, complex_number.imag)\n```\n\n```output\n42 3.5 3.0\n```\n:::\n\n:::cell\nInteger literals also accept hexadecimal (`0x`), binary (`0b`), and octal (`0o`) prefixes. Underscores group digits visually without changing the value.\n\n```python\nflags = 0xFF\nmask = 0b1010\nmillion = 1_000_000\nprint(flags, mask, million)\n```\n\n```output\n255 10 1000000\n```\n:::\n\n:::cell\nString literals write Unicode text. Raw strings keep backslashes literal, bytes literals write binary data rather than text, and f-strings (`f"..."`) embed expressions inline.\n\n```python\ntext = "python"\nraw_pattern = r"\\d+"\ndata = b"py"\nscore = 98\nformatted = f"score={score}"\nprint(text)\nprint(raw_pattern)\nprint(data)\nprint(formatted)\n```\n\n```output\npython\n\\d+\nb\'py\'\nscore=98\n```\n:::\n\n:::cell\nContainer literals create tuples, lists, dictionaries, and sets. Each container answers a different question about order, position, lookup, or uniqueness.\n\n```python\npoint = (2, 3)\nnames = ["Ada", "Grace"]\nscores = {"Ada": 98}\nunique = {"py", "go"}\nprint(point)\nprint(names[0])\nprint(scores["Ada"])\nprint(sorted(unique))\n```\n\n```output\n(2, 3)\nAda\n98\n[\'go\', \'py\']\n```\n:::\n\n:::cell\n`True`, `False`, `None`, and `...` are singleton literal-like constants used for truth values, absence, and placeholders.\n\n```python\nprint(True, False, None)\nprint(...)\n```\n\n```output\nTrue False None\nEllipsis\n```\n:::\n\n:::cell\nCurly-brace literals are dictionaries by default. The empty form `{}` is an empty dictionary, not an empty set; use `set()` for that. A non-empty `{1, 2}` is a set because keyless items can only be a set.\n\n```python\nprint(type({}).__name__)\nprint(type(set()).__name__)\nprint(type({1, 2}).__name__)\n```\n\n```output\ndict\nset\nset\n```\n:::\n\n:::note\n- Literals are good for small local values; constants are better for repeated values with meaning.\n- `{}` is an empty dictionary. Use `set()` for an empty set.\n- Bytes literals are binary data; string literals are Unicode text.\n- `...` evaluates to the `Ellipsis` object.\n:::\n', 'logging.md': '+++\nslug = "logging"\ntitle = "Logging"\nsection = "Standard Library"\nsummary = "logging records operational events without using print as infrastructure."\ndoc_path = "/library/logging.html"\n+++\n\n`logging` records operational events without using `print` as infrastructure. Loggers name where each event came from, handlers route records to outputs, and levels (`DEBUG`, `INFO`, `WARNING`, `ERROR`) let operators choose how much detail they want to see.\n\nUse it when a program needs structured records with thresholds — production services, command-line tools, scheduled jobs. Prefer plain `print` when a small script just needs to show a line of human output.\n\nThe example wires a single stream handler to stdout to keep the output deterministic. Real applications usually configure logging once at startup and then call `logging.getLogger(__name__)` from each module.\n\n:::program\n```python\nimport logging\nimport sys\n\nlogger = logging.getLogger("example")\nlogger.setLevel(logging.INFO)\nhandler = logging.StreamHandler(sys.stdout)\nhandler.setFormatter(logging.Formatter("%(levelname)s:%(message)s"))\nlogger.handlers[:] = [handler]\n\nlogger.debug("hidden")\nlogger.info("service started")\nlogger.warning("disk almost full")\n```\n:::\n\n:::cell\nLoggers name where an event came from.\n\n```python\nimport logging\nimport sys\n\nlogger = logging.getLogger("example")\nlogger.setLevel(logging.INFO)\nhandler = logging.StreamHandler(sys.stdout)\nhandler.setFormatter(logging.Formatter("%(levelname)s:%(message)s"))\nlogger.handlers[:] = [handler]\n\nlogger.debug("hidden")\nlogger.info("service started")\nlogger.warning("disk almost full")\n```\n\n```output\nINFO:service started\nWARNING:disk almost full\n```\n:::\n\n:::note\n- Loggers name where an event came from.\n- Handlers decide where records go.\n- Levels let operators choose how much detail to see.\n:::\n', 'loop-else.md': '+++\nslug = "loop-else"\ntitle = "Loop Else"\nsection = "Control Flow"\nsummary = "A loop else block runs only when the loop did not end with break."\ndoc_path = "/tutorial/controlflow.html#else-clauses-on-loops"\nsee_also = [\n "break-and-continue",\n "for-loops",\n "while-loops",\n]\n+++\n\nPython loops can have an `else` clause. The name is surprising at first: loop `else` means “no `break` happened,” not “the loop condition was false.”\n\nThis is useful for searches. Put the successful early exit in `break`, then put the not-found path in `else`.\n\nUse loop `else` sparingly. It is clearest when the loop is visibly searching for something.\n\n:::program\n```python\nnames = ["Ada", "Grace", "Guido"]\n\nfor name in names:\n if name == "Grace":\n print("found")\n break\nelse:\n print("missing")\n\nfor name in names:\n if name == "Linus":\n print("found")\n break\nelse:\n print("missing")\n```\n:::\n\n:::cell\nIf the loop reaches `break`, the `else` block is skipped. This branch means the search succeeded early.\n\n```python\nnames = ["Ada", "Grace", "Guido"]\n\nfor name in names:\n if name == "Grace":\n print("found")\n break\nelse:\n print("missing")\n```\n\n```output\nfound\n```\n:::\n\n:::cell\nIf the loop finishes without `break`, the `else` block runs. This branch means the search examined every value and found nothing.\n\n```python\nfor name in names:\n if name == "Linus":\n print("found")\n break\nelse:\n print("missing")\n```\n\n```output\nmissing\n```\n:::\n\n:::note\n- Loop `else` runs when the loop was not ended by `break`.\n- It is best for search loops with a clear found/not-found split.\n- It works with both `for` and `while` loops.\n:::\n', 'match-statements.md': '+++\nslug = "match-statements"\ntitle = "Match Statements"\nsection = "Control Flow"\nsummary = "match selects cases using structural pattern matching."\ndoc_path = "/tutorial/controlflow.html#match-statements"\n+++\n\nStructural pattern matching lets a program choose a branch based on the shape of data. It is especially useful when commands, messages, or parsed data have a few known forms.\n\nA `case` pattern can both check constants and bind names. The move case checks the action and extracts `x` and `y` in one readable step.\n\nOrder matters because Python tries cases from top to bottom. Specific shapes should appear before broad fallback cases such as `_`.\n\n:::program\n```python\ncommand = {"action": "move", "x": 3, "y": 4}\n\nmatch command:\n case {"action": "move", "x": x, "y": y}:\n print(f"move to {x},{y}")\n case {"action": "quit"}:\n print("quit")\n case {"action": action}:\n print(f"unknown action: {action}")\n case _:\n print("invalid command")\n```\n:::\n\n:::cell\nUse `match` when the shape of a value is the decision. This command is a dictionary with an action and coordinates; the first case checks that shape and binds `x` and `y`.\n\n```python\ncommand = {"action": "move", "x": 3, "y": 4}\n\nmatch command:\n case {"action": "move", "x": x, "y": y}:\n print(f"move to {x},{y}")\n```\n\n```output\nmove to 3,4\n```\n:::\n\n:::cell\nOther cases describe other valid shapes. This complete fragment changes the command so the `quit` case is the first matching pattern.\n\n```python\ncommand = {"action": "quit"}\n\nmatch command:\n case {"action": "move", "x": x, "y": y}:\n print(f"move to {x},{y}")\n case {"action": "quit"}:\n print("quit")\n```\n\n```output\nquit\n```\n:::\n\n:::cell\nBroader patterns and the `_` catch-all belong after specific cases. This fragment extracts an unknown action before the final fallback would run.\n\n```python\ncommand = {"action": "jump"}\n\nmatch command:\n case {"action": "move", "x": x, "y": y}:\n print(f"move to {x},{y}")\n case {"action": "quit"}:\n print("quit")\n case {"action": action}:\n print(f"unknown action: {action}")\n case _:\n print("invalid command")\n```\n\n```output\nunknown action: jump\n```\n:::\n\n:::note\n- `match` compares structure, not just equality.\n- Patterns can bind names such as `x` and `y` while matching.\n- Put the catch-all `_` case last, because cases are tried from top to bottom.\n:::\n', 'metaclasses.md': '+++\nslug = "metaclasses"\ntitle = "Metaclasses"\nsection = "Classes"\nsummary = "A metaclass customizes how classes themselves are created."\ndoc_path = "/reference/datamodel.html#metaclasses"\nsee_also = [\n "classes",\n "inheritance-and-super",\n "special-methods",\n]\n+++\n\nA metaclass is the class of a class. Most Python code never needs one, but the syntax appears in frameworks that register, validate, or modify classes as they are created.\n\nThe `metaclass=` keyword in a class statement chooses the object that builds the class. This is advanced machinery; decorators and ordinary functions are usually simpler.\n\nUse metaclasses only when class creation itself is the problem being solved.\n\n:::program\n```python\nclass Tagged(type):\n def __new__(mcls, name, bases, namespace):\n namespace["tag"] = name.lower()\n return super().__new__(mcls, name, bases, namespace)\n\nclass Event(metaclass=Tagged):\n pass\n\nprint(Event.tag)\nprint(type(Event).__name__)\n```\n:::\n\n:::cell\nA metaclass customizes class creation. `__new__` receives the class name, bases, and namespace before the class object exists.\n\n```python\nclass Tagged(type):\n def __new__(mcls, name, bases, namespace):\n namespace["tag"] = name.lower()\n return super().__new__(mcls, name, bases, namespace)\n\nprint(Tagged.__name__)\n```\n\n```output\nTagged\n```\n:::\n\n:::cell\nThe `metaclass=` keyword applies that class-building rule. Here the metaclass adds a `tag` attribute to the new class.\n\n```python\nclass Event(metaclass=Tagged):\n pass\n\nprint(Event.tag)\nprint(type(Event).__name__)\n```\n\n```output\nevent\nTagged\n```\n:::\n\n:::note\n- Metaclasses customize class creation, not instance behavior directly.\n- Most code should prefer class decorators, functions, or ordinary inheritance.\n- You are most likely to meet metaclasses inside frameworks and ORMs.\n:::\n', 'modules.md': '+++\nslug = "modules"\ntitle = "Modules"\nsection = "Modules"\nsummary = "Modules organize code into namespaces and expose reusable definitions."\ndoc_path = "/tutorial/modules.html"\nsee_also = [\n "import-aliases",\n "packages",\n]\n+++\n\nModules organize Python code into files and namespaces. `import` executes a module once, stores it in Python\'s import cache, and gives your program access to its definitions.\n\nThis page focuses on import forms and module namespaces. Package layout, aliases, and dynamic imports have their own neighboring examples.\n\nUse module namespaces such as `math.sqrt` when the source of a name should stay visible. Use focused imports such as `from statistics import mean` when the imported name is clear at the call site.\n\n:::program\n```python\nimport math\nimport sys\nfrom statistics import mean\n\nradius = 3\narea = math.pi * radius ** 2\nprint(round(area, 2))\n\nscores = [8, 10, 9]\nprint(mean(scores))\n\nprint(math.__name__)\nprint("math" in sys.modules)\n```\n:::\n\n:::cell\nImporting a module gives access to its namespace. The `math.` prefix makes it clear where `pi` came from.\n\n```python\nimport math\n\nradius = 3\narea = math.pi * radius ** 2\nprint(round(area, 2))\n```\n\n```output\n28.27\n```\n:::\n\n:::cell\nA focused `from ... import ...` brings one definition into the current namespace. This keeps a common operation concise without importing every name.\n\n```python\nfrom statistics import mean\n\nscores = [8, 10, 9]\nprint(mean(scores))\n```\n\n```output\n9\n```\n:::\n\n:::cell\nModules are objects too. Their attributes include metadata such as `__name__`, which records the module\'s import name.\n\n```python\nprint(math.__name__)\n```\n\n```output\nmath\n```\n:::\n\n:::cell\nImported modules are cached in `sys.modules`. Later imports reuse the module object instead of executing the file again.\n\n```python\nimport sys\nprint("math" in sys.modules)\n```\n\n```output\nTrue\n```\n:::\n\n:::note\n- Prefer plain `import module` when the namespace improves readability.\n- Use focused imports for a small number of clear names.\n- Place imports near the top of the file.\n- Imports execute module top-level code once, then reuse the cached module object.\n:::\n', 'multiple-return-values.md': '+++\nslug = "multiple-return-values"\ntitle = "Multiple Return Values"\nsection = "Functions"\nsummary = "Python returns multiple values by returning a tuple and unpacking it."\ndoc_path = "/tutorial/datastructures.html#tuples-and-sequences"\n+++\n\nPython multiple return values are tuple return values with friendly syntax. `return a, b` creates one tuple containing two positions.\n\nMost callers unpack that tuple immediately. Good target names make the meaning of each returned position explicit.\n\nUse this for small, fixed groups of results. For larger records, a dataclass or named tuple usually communicates better.\n\n:::program\n```python\ndef divide_with_remainder(total, size):\n quotient = total // size\n remainder = total % size\n return quotient, remainder\n\nresult = divide_with_remainder(17, 5)\nprint(result)\n\nboxes, leftover = result\nprint(boxes)\nprint(leftover)\n```\n:::\n\n:::cell\nReturning values separated by commas returns one tuple. The tuple is visible if the caller stores the result directly.\n\n```python\ndef divide_with_remainder(total, size):\n quotient = total // size\n remainder = total % size\n return quotient, remainder\n\nresult = divide_with_remainder(17, 5)\nprint(result)\n```\n\n```output\n(3, 2)\n```\n:::\n\n:::cell\nCallers usually unpack the tuple immediately or soon after. The names at the call site document what each position means.\n\n```python\nboxes, leftover = result\nprint(boxes)\nprint(leftover)\n```\n\n```output\n3\n2\n```\n:::\n\n:::note\n- A comma creates a tuple; `return a, b` returns one tuple containing two values.\n- Unpacking at the call site gives each returned position a meaningful name.\n- Use a class-like record when the result has many fields.\n:::\n', 'mutability.md': '+++\nslug = "mutability"\ntitle = "Mutability"\nsection = "Data Model"\nsummary = "Some objects change in place, while others return new values."\ndoc_path = "/reference/datamodel.html#objects-values-and-types"\n+++\n\nObjects in Python can be mutable or immutable. Mutable objects such as lists and dictionaries can change in place, while immutable objects such as strings and tuples produce new values instead.\n\nNames can share one mutable object, so a change through one name is visible through another. This is powerful, but it is also the source of many beginner surprises.\n\nThe boundary matters across Python: `append()` mutates a list, string methods return new strings, and `sorted()` returns a new list while `list.sort()` mutates an existing one.\n\n:::program\n```python\nfirst = ["python"]\nsecond = first\nsecond.append("workers")\nprint(first)\nprint(second)\n\ntext = "python"\nupper_text = text.upper()\nprint(text)\nprint(upper_text)\n\nnumbers = [3, 1, 2]\nordered = sorted(numbers)\nprint(ordered)\nprint(numbers)\n```\n:::\n\n:::cell\nMutable objects can change in place. `first` and `second` point to the same list, so appending through one name changes the object seen through both names.\n\n```python\nfirst = ["python"]\nsecond = first\nsecond.append("workers")\nprint(first)\nprint(second)\n```\n\n```output\n[\'python\', \'workers\']\n[\'python\', \'workers\']\n```\n:::\n\n:::cell\nImmutable objects do not change in place. String methods such as `upper()` return a new string, leaving the original string unchanged.\n\n```python\ntext = "python"\nupper_text = text.upper()\nprint(text)\nprint(upper_text)\n```\n\n```output\npython\nPYTHON\n```\n:::\n\n:::cell\nSome APIs make the boundary explicit. `sorted()` returns a new list, while methods such as `append()` and `list.sort()` mutate an existing list.\n\n```python\nnumbers = [3, 1, 2]\nordered = sorted(numbers)\nprint(ordered)\nprint(numbers)\n```\n\n```output\n[1, 2, 3]\n[3, 1, 2]\n```\n:::\n\n:::note\n- Lists and dictionaries are mutable; strings and tuples are immutable.\n- Aliasing is useful, but copy mutable containers when independent changes are needed.\n- Pay attention to whether an operation mutates in place or returns a new value.\n:::\n', 'networking.md': '+++\nslug = "networking"\ntitle = "Networking"\nsection = "Standard Library"\nsummary = "Networking code exchanges bytes across explicit protocol boundaries."\ndoc_path = "/library/socket.html"\nexpected_output = "b\'ping\'\\nping\\n"\n+++\n\nNetworking code sends and receives bytes. Higher-level HTTP clients hide many details, but the core boundary is still explicit: text must be encoded before sending and decoded after receiving.\n\nThis example uses `socket.socketpair()` so it stays local and deterministic in ordinary Python. Real network clients often use `socket.create_connection()` or a higher-level HTTP library, but the same byte boundary applies.\n\nThe useful mental model is endpoint plus bytes plus cleanup. A socket connects two endpoints, transfers byte strings, and must be closed when the conversation is finished.\n\n:::program\n```python\nimport socket\n\nleft, right = socket.socketpair()\ntry:\n message = "ping"\n left.sendall(message.encode("utf-8"))\n data = right.recv(16)\n print(data)\n print(data.decode("utf-8"))\nfinally:\n left.close()\n right.close()\n```\n:::\n\n:::unsupported\nDynamic Workers do not provide arbitrary low-level sockets, and this app disables Dynamic Worker outbound access.\n\n```python\nleft, right = socket.socketpair()\nleft.sendall("ping".encode("utf-8"))\ndata = right.recv(16)\n```\n:::\n\n:::cell\nSockets exchange bytes. Encoding and decoding make the boundary between Python text and network data visible.\n\n```python\nimport socket\n\nleft, right = socket.socketpair()\ntry:\n message = "ping"\n left.sendall(message.encode("utf-8"))\n data = right.recv(16)\n print(data)\n print(data.decode("utf-8"))\nfinally:\n left.close()\n right.close()\n```\n\n```output\nb\'ping\'\nping\n```\n:::\n\n:::note\n- Network protocols move bytes, not Python `str` objects.\n- Close real sockets when finished, usually with a context manager or `finally` block.\n- Use high-level HTTP libraries for application HTTP unless socket-level control is the lesson.\n:::\n', 'newtype.md': '+++\nslug = "newtype"\ntitle = "NewType"\nsection = "Types"\nsummary = "NewType creates distinct static identities for runtime-compatible values."\ndoc_path = "/library/typing.html#typing.NewType"\nsee_also = [\n "type-aliases",\n "type-hints",\n "runtime-type-checks",\n]\n+++\n\n`NewType` creates a distinct static identity for a value that is represented by an existing runtime type. It is useful for IDs, units, and other values that should not be mixed accidentally.\n\nThe key boundary is static versus runtime behavior. A type checker can distinguish `UserId` from `OrderId`, but at runtime both values are plain integers.\n\nUse a type alias when you only want a clearer name for a shape. Use `NewType` when mixing two compatible shapes should be treated as a mistake by static analysis.\n\n:::program\n```python\nfrom typing import NewType\n\nUserId = NewType("UserId", int)\nOrderId = NewType("OrderId", int)\n\n\ndef load_user(user_id: UserId) -> str:\n return f"user {user_id}"\n\nuid = UserId(42)\noid = OrderId(42)\nprint(load_user(uid))\nprint(uid == oid)\nprint(type(uid).__name__)\nprint(UserId.__name__)\n```\n:::\n\n:::cell\n`NewType` helps type checkers distinguish values that share a runtime representation.\n\n```python\nfrom typing import NewType\n\nUserId = NewType("UserId", int)\nOrderId = NewType("OrderId", int)\n\n\ndef load_user(user_id: UserId) -> str:\n return f"user {user_id}"\n\nuid = UserId(42)\nprint(load_user(uid))\n```\n\n```output\nuser 42\n```\n:::\n\n:::cell\nAt runtime, a `NewType` value is the underlying value. It compares like that value and has the same runtime type.\n\n```python\noid = OrderId(42)\nprint(uid == oid)\nprint(type(uid).__name__)\n```\n\n```output\nTrue\nint\n```\n:::\n\n:::cell\nThe `NewType` constructor keeps a name for static tools and introspection.\n\n```python\nprint(UserId.__name__)\nprint(OrderId.__name__)\n```\n\n```output\nUserId\nOrderId\n```\n:::\n\n:::note\n- `NewType` helps type checkers distinguish values that share a runtime representation.\n- At runtime, the value is still the underlying type.\n- Use aliases for readability; use `NewType` for static separation.\n:::\n', 'none.md': '+++\nslug = "none"\ntitle = "None"\nsection = "Basics"\nsummary = "None represents expected absence, distinct from missing keys and errors."\ndoc_path = "/library/constants.html#None"\n+++\n\n`None` represents the absence of a value. It is the usual sentinel when a function has no result to return but the absence itself is meaningful.\n\nBecause `None` is a singleton, idiomatic Python checks it with `is None` or `is not None`. This avoids confusing identity with value equality.\n\nAbsence has several nearby shapes in Python. A function can return `None`, a dictionary lookup can supply a default for a missing key, and an invalid operation can raise an exception.\n\n:::program\n```python\nresult = None\nprint(result is None)\n\ndef find_score(name):\n if name == "Ada":\n return 10\n return None\n\nscore = find_score("Grace")\nprint(score is None)\n\nprofile = {"name": "Ada"}\nprint(profile.get("timezone", "UTC"))\n\ntry:\n int("python")\nexcept ValueError:\n print("invalid number")\n```\n:::\n\n:::cell\n`None` is Python\'s value for “nothing here.” Check it with `is None` because it is a singleton identity value.\n\n```python\nresult = None\nprint(result is None)\n```\n\n```output\nTrue\n```\n:::\n\n:::cell\nFunctions often return `None` when absence is expected and callers can continue. The function name and surrounding code should make that possibility clear.\n\n```python\ndef find_score(name):\n if name == "Ada":\n return 10\n return None\n\nscore = find_score("Grace")\nprint(score is None)\n```\n\n```output\nTrue\n```\n:::\n\n:::cell\nA missing dictionary key is another absence boundary. Use `get()` when the mapping can supply a default, and use exceptions for invalid operations that cannot produce a value.\n\n```python\nprofile = {"name": "Ada"}\nprint(profile.get("timezone", "UTC"))\n\ntry:\n int("python")\nexcept ValueError:\n print("invalid number")\n```\n\n```output\nUTC\ninvalid number\n```\n:::\n\n:::note\n- Use `is None` rather than `== None`; `None` is a singleton identity value.\n- Use `None` for expected absence that callers can test.\n- Use dictionary defaults for missing mapping keys and exceptions for invalid operations.\n:::\n', 'number-parsing.md': '+++\nslug = "number-parsing"\ntitle = "Number Parsing"\nsection = "Standard Library"\nsummary = "int() and float() parse text into numbers and raise ValueError on bad input."\ndoc_path = "/library/functions.html#int"\n+++\n\nParsing turns text into numeric values. `int()` parses whole-number text and `float()` parses decimal or scientific-notation text.\n\nInvalid numeric text raises `ValueError`. Catching that specific exception makes it clear that bad input is expected and recoverable.\n\nAfter parsing, the result is a number and can participate in arithmetic; before parsing, it is just text.\n\n:::program\n```python\nprint(int("42"))\nprint(float("3.5"))\n\ntry:\n int("python")\nexcept ValueError as error:\n print(type(error).__name__)\n```\n:::\n\n:::cell\nUse `int()` for whole numbers and `float()` for decimal text. Parsed values are real numbers, not strings.\n\n```python\nprint(int("42"))\nprint(float("3.5"))\n```\n\n```output\n42\n3.5\n```\n:::\n\n:::cell\nBad numeric text raises `ValueError`. Catch that specific exception when invalid input is part of the normal flow.\n\n```python\ntry:\n int("python")\nexcept ValueError as error:\n print(type(error).__name__)\n```\n\n```output\nValueError\n```\n:::\n\n:::note\n- `int()` and `float()` are constructors that also parse strings.\n- Catch `ValueError` when bad user input is expected and recoverable.\n:::\n', 'numbers.md': '+++\nslug = "numbers"\ntitle = "Numbers"\nsection = "Basics"\nsummary = "Python numbers include integers, floats, and complex values."\ndoc_path = "/library/stdtypes.html#numeric-types-int-float-complex"\nsee_also = [\n "literals",\n "operators",\n]\n+++\n\nPython\'s numeric model starts with `int`, `float`, and `complex`. Integers are arbitrary precision, floats are approximate double-precision values, and complex numbers carry real and imaginary parts.\n\nOperators encode different numeric questions. `/` means true division and returns a float, `//` means floor division, `%` gives the remainder, and `**` computes powers.\n\nUse rounding for display, not as a substitute for understanding floating-point approximation. Financial code usually needs `decimal.Decimal`, which is a separate precision topic.\n\n:::program\n```python\nimport math\n\ncount = 10\nratio = 0.25\nz = 2 + 3j\n\nprint(count + 5)\nprint(count / 4)\nprint(count // 4)\nprint(count % 4)\nprint(2 ** 5)\nprint(z.real, z.imag)\nprint(0.1 + 0.2)\nprint(0.1 + 0.2 == 0.3)\nprint(math.isclose(0.1 + 0.2, 0.3))\nprint(round(3.14159, 2))\n```\n:::\n\n:::cell\nPython has `int` for whole numbers and `float` for approximate real-valued arithmetic. True division with `/` returns a `float`, even when both inputs are integers.\n\n```python\ncount = 10\nratio = 0.25\n\nprint(count + 5)\nprint(count / 4)\nprint(ratio * 2)\n```\n\n```output\n15\n2.5\n0.5\n```\n:::\n\n:::cell\nFloor division and modulo are useful when you need quotient and remainder behavior. Powers use `**`, not `^`.\n\n```python\nprint(count // 4)\nprint(count % 4)\nprint(2 ** 5)\n```\n\n```output\n2\n2\n32\n```\n:::\n\n:::cell\nComplex numbers are built in. The literal suffix `j` marks the imaginary part.\n\n```python\nz = 2 + 3j\nprint(z.real, z.imag)\n```\n\n```output\n2.0 3.0\n```\n:::\n\n:::cell\nFloating-point values are approximate, so `==` between expected and computed floats is rarely the right test. Compare with `math.isclose` (or work in `decimal.Decimal`) when the question is "are these the same number to within tolerance".\n\n```python\nimport math\n\nprint(0.1 + 0.2)\nprint(0.1 + 0.2 == 0.3)\nprint(math.isclose(0.1 + 0.2, 0.3))\nprint(round(3.14159, 2))\n```\n\n```output\n0.30000000000000004\nFalse\nTrue\n3.14\n```\n:::\n\n:::note\n- Python\'s `int` has arbitrary precision; it grows as large as memory allows.\n- Python\'s `float` is approximate double-precision floating point.\n- Use `/` for true division and `//` for floor division.\n- Use `math.isclose` instead of `==` for floating-point comparison; reach for `decimal.Decimal` when exact decimal precision is the domain requirement.\n:::\n', 'object-lifecycle.md': '+++\nslug = "object-lifecycle"\ntitle = "Object Lifecycle"\nsection = "Basics"\nsummary = "References keep objects alive until Python can reclaim them."\ndoc_path = "/reference/datamodel.html#objects-values-and-types"\n+++\n\nReferences keep objects alive until Python can reclaim them. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.\n\nUse it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.\n\nThe example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.\n\n:::program\n```python\nimport gc\n\nnames = []\nalias = names\nalias.append("Ada")\n\nprint(names is alias)\nprint(names)\n\nobject_id = id(names)\ndel alias\nprint(id(names) == object_id)\n\ndel names\nprint("object can be reclaimed")\ngc.collect()\n```\n:::\n\n:::cell\nUse `is` and `id()` to observe identity while two names refer to the same object.\n\n```python\nimport gc\n\nnames = []\nalias = names\nalias.append("Ada")\n\nprint(names is alias)\nprint(names)\n\nobject_id = id(names)\ndel alias\nprint(id(names) == object_id)\n\ndel names\nprint("object can be reclaimed")\ngc.collect()\n```\n\n```output\nTrue\n[\'Ada\']\nTrue\nobject can be reclaimed\n```\n:::\n\n:::note\n- Use `is` and `id()` to observe identity while two names refer to the same object.\n- Deleting a name removes one reference; it does not directly destroy the object if another reference still exists.\n- Python reclaims unreachable objects automatically, so programs usually manage ownership by controlling references.\n:::\n', 'operator-overloading.md': '+++\nslug = "operator-overloading"\ntitle = "Operator Overloading"\nsection = "Data Model"\nsummary = "Operator methods let objects define arithmetic and comparison syntax."\ndoc_path = "/reference/datamodel.html#emulating-numeric-types"\nsee_also = [\n "operators",\n "special-methods",\n "equality-and-identity",\n]\n+++\n\nOperator overloading lets a class define what expressions such as `a + b` mean for its objects. This is useful when the operation is part of the domain vocabulary.\n\nThe method should preserve the meaning readers expect from the operator. Vectors can add component by component; money can add amounts in the same currency; surprising overloads make code harder to trust.\n\nPython also has reflected methods such as `__radd__` for cases where the left operand does not know how to handle the right operand. That keeps mixed operations possible without making every type know every other type.\n\n:::program\n```python\nclass Vector:\n def __init__(self, x, y):\n self.x = x\n self.y = y\n\n def __add__(self, other):\n return Vector(self.x + other.x, self.y + other.y)\n\n def __eq__(self, other):\n return (self.x, self.y) == (other.x, other.y)\n\n def __repr__(self):\n return f"Vector({self.x}, {self.y})"\n\nprint(Vector(2, 3) + Vector(4, 5))\nprint(Vector(1, 1) == Vector(1, 1))\n```\n:::\n\n:::cell\n`__add__` defines how the `+` operator combines two objects.\n\n```python\nclass Vector:\n def __init__(self, x, y):\n self.x = x\n self.y = y\n\n def __add__(self, other):\n return Vector(self.x + other.x, self.y + other.y)\n\n def __repr__(self):\n return f"Vector({self.x}, {self.y})"\n\nprint(Vector(2, 3) + Vector(4, 5))\n```\n\n```output\nVector(6, 8)\n```\n:::\n\n:::cell\n`__eq__` defines value equality for `==`. Without it, user-defined objects compare by identity.\n\n```python\nclass Vector:\n def __init__(self, x, y):\n self.x = x\n self.y = y\n\n def __eq__(self, other):\n return (self.x, self.y) == (other.x, other.y)\n\nprint(Vector(1, 1) == Vector(1, 1))\n```\n\n```output\nTrue\n```\n:::\n\n:::cell\nA useful `__repr__` makes operator results inspectable while debugging.\n\n```python\nclass Vector:\n def __init__(self, x, y):\n self.x = x\n self.y = y\n\n def __add__(self, other):\n return Vector(self.x + other.x, self.y + other.y)\n\n def __repr__(self):\n return f"Vector({self.x}, {self.y})"\n\nprint(repr(Vector(2, 3) + Vector(4, 5)))\n```\n\n```output\nVector(6, 8)\n```\n:::\n\n:::note\n- Overload operators only when the operation is unsurprising.\n- Return `NotImplemented` when an operand type is unsupported.\n- Implement equality deliberately when value comparison matters.\n:::\n', 'operators.md': '+++\nslug = "operators"\ntitle = "Operators"\nsection = "Basics"\nsummary = "Operators combine, compare, and test values in expressions."\ndoc_path = "/reference/expressions.html#operator-precedence"\nsee_also = [\n "numbers",\n "equality-and-identity",\n "assignment-expressions",\n "operator-overloading",\n]\n+++\n\nOperators are the punctuation and keywords that combine values into expressions. Some operators compute new values, some compare values, and some ask relationship questions such as membership or identity.\n\nThis page is the surface map. Focused examples explain the deeper behavior of numbers, booleans, conditions, sets, assignment expressions, and operator overloading.\n\nRead operators by the question they ask: arithmetic computes, comparison answers true or false, boolean operators combine truth values, membership searches a container, and specialized operators should only appear when the data shape calls for them.\n\n:::program\n```python\ncount = 10\nprint(count + 5)\nprint(count // 4)\nprint(count % 4)\nprint(2 ** 5)\n\nscore = 91\nprint(80 <= score < 100)\nprint(score == 100 or score >= 90)\nprint("py" in "python")\n\nflags = 0b0011\nprint(flags & 0b0101)\nprint(flags | 0b0100)\nprint(flags ^ 0b0101)\nprint(flags << 1)\n\nclass Scale:\n def __init__(self, value):\n self.value = value\n\n def __matmul__(self, other):\n return self.value * other.value\n\nprint(Scale(2) @ Scale(3))\n\nitems = ["a", "b"]\nif (size := len(items)) > 0:\n print(size)\n```\n:::\n\n:::cell\nArithmetic operators compute new values. Use `//` for floor division, `%` for remainder, and `**` for powers.\n\n```python\ncount = 10\nprint(count + 5)\nprint(count // 4)\nprint(count % 4)\nprint(2 ** 5)\n```\n\n```output\n15\n2\n2\n32\n```\n:::\n\n:::cell\nComparison operators produce booleans. Python comparisons can chain, which keeps range checks readable.\n\n```python\nscore = 91\nprint(80 <= score < 100)\nprint(score == 100 or score >= 90)\nprint("py" in "python")\n```\n\n```output\nTrue\nTrue\nTrue\n```\n:::\n\n:::cell\nBitwise operators work on integer bit patterns. They are useful for masks and flags, not ordinary boolean logic. `&` is bitwise AND, `|` is bitwise OR, `^` is exclusive OR, and `<<` shifts left.\n\n```python\nflags = 0b0011\nprint(flags & 0b0101)\nprint(flags | 0b0100)\nprint(flags ^ 0b0101)\nprint(flags << 1)\n```\n\n```output\n1\n7\n6\n6\n```\n:::\n\n:::cell\nThe `@` operator is reserved for matrix-like multiplication and custom types that define `__matmul__`.\n\n```python\nclass Scale:\n def __init__(self, value):\n self.value = value\n\n def __matmul__(self, other):\n return self.value * other.value\n\nprint(Scale(2) @ Scale(3))\n```\n\n```output\n6\n```\n:::\n\n:::cell\nThe walrus operator `:=` assigns inside an expression. Use it when naming a value avoids repeating work in a condition.\n\n```python\nitems = ["a", "b"]\nif (size := len(items)) > 0:\n print(size)\n```\n\n```output\n2\n```\n:::\n\n:::cell\n`and` and `or` short-circuit: the right side runs only when the left side cannot already determine the result. That makes them safe for guard expressions like `obj and obj.value` where the right side would fail on `None`.\n\n```python\ndef loud():\n print("ran")\n return True\n\nprint(False and loud())\nprint(True or loud())\nprint(True and loud())\n```\n\n```output\nFalse\nTrue\nran\nTrue\n```\n:::\n\n:::note\n- Use the clearest operator for the question: arithmetic, comparison, boolean logic, membership, identity, or bitwise manipulation.\n- `and` and `or` short-circuit, so the right side may not run.\n- Operators have precedence; use parentheses when grouping would otherwise be hard to read.\n- Custom operator behavior should make an object feel more natural, not more clever.\n:::\n', 'overloads.md': '+++\nslug = "overloads"\ntitle = "Overloads"\nsection = "Types"\nsummary = "overload describes APIs whose return type depends on argument types."\ndoc_path = "/library/typing.html#typing.overload"\n+++\n\noverload describes APIs whose return type depends on argument types. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.\n\nUse it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.\n\nThe example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.\n\n:::program\n```python\nfrom typing import overload\n\n@overload\ndef double(value: int) -> int: ...\n\n@overload\ndef double(value: str) -> str: ...\n\ndef double(value):\n return value * 2\n\nprint(double(4))\nprint(double("ha"))\nprint(double.__name__)\n```\n:::\n\n:::cell\n`@overload` signatures are for static type checkers.\n\n```python\nfrom typing import overload\n\n@overload\ndef double(value: int) -> int: ...\n\n@overload\ndef double(value: str) -> str: ...\n\ndef double(value):\n return value * 2\n\nprint(double(4))\nprint(double("ha"))\nprint(double.__name__)\n```\n\n```output\n8\nhaha\ndouble\n```\n:::\n\n:::note\n- `@overload` signatures are for static type checkers.\n- The real implementation comes after the overload declarations.\n- Use overloads when a single runtime function has multiple precise static shapes.\n:::\n', 'packages.md': '+++\nslug = "packages"\ntitle = "Packages"\nsection = "Modules"\nsummary = "Packages organize modules into importable directories."\ndoc_path = "/tutorial/modules.html#packages"\nsee_also = [\n "modules",\n "import-aliases",\n "virtual-environments",\n]\n+++\n\nPackages are modules that can contain other modules. They let a project group related code behind dotted import paths such as `json.decoder` or `email.message`.\n\nAt runtime, importing a submodule gives Python a path through that package structure. In a project on disk, that structure is usually a directory with Python files and often an `__init__.py` file.\n\nUse packages when one module has grown into a small namespace of related modules. Keep module names boring and explicit so readers can tell where imported definitions come from.\n\n:::program\n```python\nimport importlib\nimport json\nimport json.decoder\n\nmodule = importlib.import_module("json.decoder")\n\nprint(json.__name__)\nprint(json.decoder.__name__)\nprint(module.JSONDecoder.__name__)\nprint(module is json.decoder)\n\n\nimport os\nimport sys\nimport tempfile\n\nwith tempfile.TemporaryDirectory() as tmp:\n pkg = os.path.join(tmp, "shapes")\n os.makedirs(pkg)\n with open(os.path.join(pkg, "__init__.py"), "w") as init:\n init.write("from .square import area\\n__all__ = [\'area\']\\n")\n with open(os.path.join(pkg, "square.py"), "w") as square:\n square.write("def area(side):\\n return side * side\\n")\n sys.path.insert(0, tmp)\n try:\n import shapes\n print(shapes.area(3))\n print(shapes.__all__)\n finally:\n sys.path.remove(tmp)\n sys.modules.pop("shapes", None)\n sys.modules.pop("shapes.square", None)\n```\n:::\n\n:::cell\nA package is itself a module. The `json` package exposes a namespace that can contain submodules.\n\n```python\nimport json\n\nprint(json.__name__)\n```\n\n```output\njson\n```\n:::\n\n:::cell\nDotted imports name a path through a package. Importing `json.decoder` makes that submodule available under the package namespace.\n\n```python\nimport json.decoder\n\nprint(json.decoder.__name__)\nprint(json.decoder.JSONDecoder.__name__)\n```\n\n```output\njson.decoder\nJSONDecoder\n```\n:::\n\n:::cell\n`importlib.import_module()` imports by string. It is useful for plugin systems and dynamic imports, but ordinary `import` is clearer when the dependency is known.\n\n```python\nimport importlib\n\nmodule = importlib.import_module("json.decoder")\nprint(module is json.decoder)\n```\n\n```output\nTrue\n```\n:::\n\n:::cell\nInside a package\'s `__init__.py`, `from .submodule import name` re-exports a submodule\'s name at the package root, and `__all__` lists the names that `from package import *` should make visible. This cell builds a temporary `shapes` package on disk to make both forms concrete.\n\n```python\nimport os\nimport sys\nimport tempfile\n\nwith tempfile.TemporaryDirectory() as tmp:\n pkg = os.path.join(tmp, "shapes")\n os.makedirs(pkg)\n with open(os.path.join(pkg, "__init__.py"), "w") as init:\n init.write("from .square import area\\n__all__ = [\'area\']\\n")\n with open(os.path.join(pkg, "square.py"), "w") as square:\n square.write("def area(side):\\n return side * side\\n")\n sys.path.insert(0, tmp)\n try:\n import shapes\n print(shapes.area(3))\n print(shapes.__all__)\n finally:\n sys.path.remove(tmp)\n sys.modules.pop("shapes", None)\n sys.modules.pop("shapes.square", None)\n```\n\n```output\n9\n[\'area\']\n```\n:::\n\n:::note\n- A package is a module that can contain submodules.\n- Dotted imports should mirror a meaningful project structure.\n- Use `from .submodule import name` inside a package to re-export submodule names; set `__all__` to declare the public surface.\n- Prefer ordinary imports unless the module name is truly dynamic.\n:::\n', 'paramspec.md': '+++\nslug = "paramspec"\ntitle = "ParamSpec"\nsection = "Types"\nsummary = "ParamSpec preserves callable parameter types through wrappers."\ndoc_path = "/library/typing.html#typing.ParamSpec"\n+++\n\n`ParamSpec` lets a wrapper preserve the parameter types of the function it wraps. The pressure that justifies it is decorators: a generic decorator that returns `Callable[..., R]` erases the wrapped function\'s argument types, so callers lose type-checker help on every call.\n\nUse `ParamSpec` when a decorator should be transparent to type checkers — the wrapped function and the decorated name should accept the same arguments. Reach for plain `Callable` when the wrapper deliberately changes the signature.\n\n`P.args` and `P.kwargs` annotate the `*args` and `**kwargs` of the inner wrapper, which is how the parameter spec gets bound. Pair `ParamSpec` with a `TypeVar` for the return type when the wrapper should also stay generic over what the wrapped function returns.\n\n:::program\n```python\nfrom collections.abc import Callable\nfrom typing import ParamSpec, TypeVar\n\nP = ParamSpec("P")\nR = TypeVar("R")\n\n\ndef logged(func: Callable[P, R]) -> Callable[P, R]:\n def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:\n print("calling", func.__name__)\n return func(*args, **kwargs)\n return wrapper\n\n@logged\ndef add(left: int, right: int) -> int:\n return left + right\n\nprint(add(2, 3))\n```\n:::\n\n:::cell\n`ParamSpec` captures the parameters of a callable.\n\n```python\nfrom collections.abc import Callable\nfrom typing import ParamSpec, TypeVar\n\nP = ParamSpec("P")\nR = TypeVar("R")\n\n\ndef logged(func: Callable[P, R]) -> Callable[P, R]:\n def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:\n print("calling", func.__name__)\n return func(*args, **kwargs)\n return wrapper\n\n@logged\ndef add(left: int, right: int) -> int:\n return left + right\n\nprint(add(2, 3))\n```\n\n```output\ncalling add\n5\n```\n:::\n\n:::note\n- `ParamSpec` captures the parameters of a callable.\n- Wrappers can forward `*args` and `**kwargs` without erasing the original signature for type checkers.\n- This matters most for decorators.\n:::\n', 'partial-functions.md': '+++\nslug = "partial-functions"\ntitle = "Partial Functions"\nsection = "Functions"\nsummary = "functools.partial pre-fills arguments to make a more specific callable."\ndoc_path = "/library/functools.html#functools.partial"\n+++\n\nfunctools.partial pre-fills arguments to make a more specific callable. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.\n\nUse it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.\n\nThe example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.\n\n:::program\n```python\nfrom functools import partial\n\n\ndef apply_tax(rate, amount):\n return round(amount * (1 + rate), 2)\n\nvat = partial(apply_tax, 0.2)\nservice_tax = partial(apply_tax, rate=0.1)\n\nprint(vat(50))\nprint(service_tax(amount=80))\nprint(vat.func.__name__)\n```\n:::\n\n:::cell\nA partial object remembers some arguments.\n\n```python\nfrom functools import partial\n\n\ndef apply_tax(rate, amount):\n return round(amount * (1 + rate), 2)\n\nvat = partial(apply_tax, 0.2)\nservice_tax = partial(apply_tax, rate=0.1)\n\nprint(vat(50))\nprint(service_tax(amount=80))\nprint(vat.func.__name__)\n```\n\n```output\n60.0\n88.0\napply_tax\n```\n:::\n\n:::note\n- A partial object remembers some arguments.\n- The resulting callable can be passed where an ordinary function is expected.\n- Prefer a named function when the pre-filled behavior needs richer logic.\n:::\n', 'positional-only-parameters.md': '+++\nslug = "positional-only-parameters"\ntitle = "Positional-only Parameters"\nsection = "Functions"\nsummary = "Use / to mark parameters that callers must pass by position."\ndoc_path = "/tutorial/controlflow.html#special-parameters"\nsee_also = [\n "keyword-only-arguments",\n "functions",\n "args-and-kwargs",\n]\n+++\n\nA `/` in a function signature marks the parameters before it as positional-only. Callers must pass those arguments by position, not by keyword.\n\nThis is useful when parameter names are implementation details or when an API should match built-in functions that accept positional values.\n\nTogether, `/` and `*` let a signature draw clear boundaries: positional-only inputs, ordinary inputs, and keyword-only options.\n\n:::program\n```python\ndef scale(value, /, factor=2, *, clamp=False):\n result = value * factor\n if clamp:\n result = min(result, 10)\n return result\n\nprint(scale(4))\nprint(scale(4, factor=3))\nprint(scale(4, clamp=True))\n```\n:::\n\n:::cell\nParameters before `/` are positional-only. `value` is the main input, while `factor` remains an ordinary parameter that can be named.\n\n```python\ndef scale(value, /, factor=2, *, clamp=False):\n result = value * factor\n if clamp:\n result = min(result, 10)\n return result\n\nprint(scale(4))\nprint(scale(4, factor=3))\n```\n\n```output\n8\n12\n```\n:::\n\n:::cell\nParameters after `*` are keyword-only. That makes options such as `clamp` explicit at the call site.\n\n```python\nprint(scale(4, clamp=True))\n```\n\n```output\n8\n```\n:::\n\n:::note\n- `/` marks parameters before it as positional-only.\n- `*` marks parameters after it as keyword-only.\n- Use these markers when the call shape is part of the API design.\n:::\n', 'properties.md': '+++\nslug = "properties"\ntitle = "Properties"\nsection = "Classes"\nsummary = "@property keeps attribute syntax while adding computation or validation."\ndoc_path = "/library/functions.html#property"\n+++\n\nProperties let a class keep a simple attribute-style API while running code behind the scenes. Callers write `box.area`, but the class can compute the value from current state.\n\nA property setter can validate assignment without changing the public spelling of the attribute. This is the boundary: plain attributes are enough for plain data, while properties are for computed or protected data.\n\nUse properties for cheap, attribute-like operations. Expensive work or actions with side effects should usually remain explicit methods.\n\n:::program\n```python\nclass Rectangle:\n def __init__(self, width, height):\n self.width = width\n self.height = height\n\n @property\n def area(self):\n return self.width * self.height\n\n @property\n def width(self):\n return self._width\n\n @width.setter\n def width(self, value):\n if value <= 0:\n raise ValueError("width must be positive")\n self._width = value\n\nbox = Rectangle(3, 4)\nprint(box.area)\n\nbox.width = 5\nprint(box.area)\n\ntry:\n box.width = 0\nexcept ValueError as error:\n print(error)\n```\n:::\n\n:::cell\nA read-only property exposes computed data through attribute access. `area` stays current because it is calculated from `width` and `height` each time it is read.\n\n```python\nclass Rectangle:\n def __init__(self, width, height):\n self.width = width\n self.height = height\n\n @property\n def area(self):\n return self.width * self.height\n\n @property\n def width(self):\n return self._width\n\n @width.setter\n def width(self, value):\n if value <= 0:\n raise ValueError("width must be positive")\n self._width = value\n\nbox = Rectangle(3, 4)\nprint(box.area)\n```\n\n```output\n12\n```\n:::\n\n:::cell\nA setter lets assignment keep normal attribute syntax while the class validates or normalizes the value.\n\n```python\nbox.width = 5\nprint(box.area)\n```\n\n```output\n20\n```\n:::\n\n:::cell\nValidation belongs inside the class when every caller should obey the same rule. Invalid assignment raises an exception at the boundary.\n\n```python\ntry:\n box.width = 0\nexcept ValueError as error:\n print(error)\n```\n\n```output\nwidth must be positive\n```\n:::\n\n:::note\n- Properties let APIs start simple and grow validation or computation later.\n- Callers access a property like an attribute, not like a method.\n- Use methods instead when work is expensive or action-like.\n:::\n', 'protocols.md': '+++\nslug = "protocols"\ntitle = "Protocols"\nsection = "Types"\nsummary = "Protocol describes required behavior for structural typing."\ndoc_path = "/library/typing.html#typing.Protocol"\nsee_also = [\n "type-hints",\n "classes",\n "inheritance-and-super",\n "abstract-base-classes",\n]\n+++\n\n`Protocol` describes the methods or attributes an object must provide. It exists for structural typing: if an object has the right shape, type checkers can treat it as compatible.\n\nThis is different from inheritance. Inheritance says a class is explicitly derived from a parent; a protocol says callers only need a particular behavior.\n\nAt runtime, ordinary method lookup still applies. Protocols are mainly for static analysis, documentation, and API boundaries.\n\n:::program\n```python\nfrom typing import Protocol\n\nclass Greeter(Protocol):\n def greet(self) -> str:\n ...\n\nclass Person:\n def __init__(self, name):\n self.name = name\n\n def greet(self):\n return f"hello {self.name}"\n\n\ndef welcome(greeter: Greeter):\n print(greeter.greet())\n\nwelcome(Person("Ada"))\nprint(Greeter.__name__)\n```\n:::\n\n:::cell\nA protocol names required behavior. The ellipsis marks the method body as intentionally unspecified, similar to an interface declaration.\n\n```python\nfrom typing import Protocol\n\nclass Greeter(Protocol):\n def greet(self) -> str:\n ...\n\nprint(Greeter.__name__)\n```\n\n```output\nGreeter\n```\n:::\n\n:::cell\nA class can satisfy the protocol without inheriting from it. `Person` has a compatible `greet()` method, so it has the right shape for static type checkers.\n\n```python\nclass Person:\n def __init__(self, name):\n self.name = name\n\n def greet(self):\n return f"hello {self.name}"\n\nprint(Person("Ada").greet())\n```\n\n```output\nhello Ada\n```\n:::\n\n:::cell\nUse the protocol as an annotation at the API boundary. The function only cares that the object can greet; it does not care about the concrete class.\n\n```python\ndef welcome(greeter: Greeter):\n print(greeter.greet())\n\nwelcome(Person("Ada"))\n```\n\n```output\nhello Ada\n```\n:::\n\n:::note\n- Protocols are for structural typing: compatibility by shape rather than explicit inheritance.\n- Type checkers understand protocols; normal runtime method calls still do the work.\n- Prefer inheritance when shared implementation matters, and protocols when only required behavior matters.\n:::\n', 'recursion.md': '+++\nslug = "recursion"\ntitle = "Recursion"\nsection = "Functions"\nsummary = "Recursive functions solve nested problems by calling themselves on smaller pieces."\ndoc_path = "/tutorial/controlflow.html#defining-functions"\n+++\n\nA recursive function calls itself to solve a smaller piece of the same problem. Recursion exists for data that is naturally nested: trees, menus, expression nodes, and directory-like structures.\n\nEvery recursive function needs a base case that can be answered directly. The recursive case must move toward that base case by passing a smaller part of the data.\n\nPrefer loops for simple repetition over a flat sequence. Prefer recursion when the data shape is recursive too.\n\n:::program\n```python\ntree = {\n "value": 1,\n "children": [\n {"value": 2, "children": []},\n {"value": 3, "children": [{"value": 4, "children": []}]},\n ],\n}\n\ndef total(node):\n subtotal = node["value"]\n for child in node["children"]:\n subtotal += total(child)\n return subtotal\n\nprint(total({"value": 2, "children": []}))\nprint(total(tree))\n```\n:::\n\n:::cell\nA leaf node is the base case. It has no children, so the function can return its own value without making another recursive call.\n\n```python\ndef total(node):\n subtotal = node["value"]\n for child in node["children"]:\n subtotal += total(child)\n return subtotal\n\nprint(total({"value": 2, "children": []}))\n```\n\n```output\n2\n```\n:::\n\n:::cell\nA non-leaf node solves the same problem for each child, then combines those smaller totals with its own value.\n\n```python\ntree = {\n "value": 1,\n "children": [\n {"value": 2, "children": []},\n {"value": 3, "children": [{"value": 4, "children": []}]},\n ],\n}\n\nprint(total(tree))\n```\n\n```output\n10\n```\n:::\n\n:::note\n- Every recursive function needs a base case that stops the calls.\n- Recursion fits nested data better than flat repetition.\n- Python limits recursion depth, so loops are often better for very deep or simple repetition.\n:::\n', 'regular-expressions.md': '+++\nslug = "regular-expressions"\ntitle = "Regular Expressions"\nsection = "Text"\nsummary = "The re module searches and extracts text using regular expressions."\ndoc_path = "/library/re.html"\nsee_also = [\n "strings",\n "string-formatting",\n]\n+++\n\nRegular expressions are a compact language for searching and extracting text patterns. Python\'s `re` module provides the standard interface: `re.match` anchors at the start of the string, `re.search` finds the first occurrence anywhere, `re.findall` collects every match, `re.sub` rewrites matches, and `re.compile` reuses a pattern.\n\nUse regex when the pattern has structure: repeated records, alternatives, optional parts, or pieces you want to capture. Prefer ordinary string methods for simple substring checks because simpler code is easier to maintain.\n\nFlags such as `re.IGNORECASE` adjust matching behavior without rewriting the pattern. Pair them with `re.compile` when the same pattern is used repeatedly.\n\n:::program\n```python\nimport re\n\ntext = "Ada: 10, Grace: 9"\npattern = r"([A-Za-z]+): (\\d+)"\n\nfor name, score in re.findall(pattern, text):\n print(name, int(score))\n\nmatch = re.search(r"Grace: (\\d+)", text)\nprint(match.group(1))\nprint("Grace" in text)\n\nstart = re.match(r"Ada", text)\nprint(start is not None)\nprint(re.match(r"Grace", text))\n\nscoreline = re.compile(pattern)\nprint(scoreline.findall(text))\n\ncasey = "ADA: 11"\nprint(re.search(r"ada", casey, flags=re.IGNORECASE).group(0))\n\nprint(re.sub(r"\\d+", "?", text))\n```\n:::\n\n:::cell\nRaw strings keep backslashes readable in regex patterns. Capturing groups return just the pieces inside parentheses.\n\n```python\nimport re\n\ntext = "Ada: 10, Grace: 9"\npattern = r"([A-Za-z]+): (\\d+)"\n\nfor name, score in re.findall(pattern, text):\n print(name, int(score))\n```\n\n```output\nAda 10\nGrace 9\n```\n:::\n\n:::cell\n`re.search()` finds the first match. A match object exposes captured groups by position.\n\n```python\nmatch = re.search(r"Grace: (\\d+)", text)\nprint(match.group(1))\n```\n\n```output\n9\n```\n:::\n\n:::cell\nFor a simple substring check, ordinary string membership is clearer than regex.\n\n```python\nprint("Grace" in text)\n```\n\n```output\nTrue\n```\n:::\n\n:::cell\n`re.match` only matches at the start of the string; `re.search` finds the first match anywhere. Picking the right one keeps anchoring intent visible without an explicit `^`.\n\n```python\nstart = re.match(r"Ada", text)\nprint(start is not None)\nprint(re.match(r"Grace", text))\n```\n\n```output\nTrue\nNone\n```\n:::\n\n:::cell\n`re.compile` produces a reusable pattern object whose methods skip the parser on each call. Reach for it when the same pattern runs in a loop.\n\n```python\nscoreline = re.compile(pattern)\nprint(scoreline.findall(text))\n```\n\n```output\n[(\'Ada\', \'10\'), (\'Grace\', \'9\')]\n```\n:::\n\n:::cell\nFlags such as `re.IGNORECASE` adjust matching without changing the pattern. `re.sub` replaces every match with a replacement string and returns the rewritten text.\n\n```python\ncasey = "ADA: 11"\nprint(re.search(r"ada", casey, flags=re.IGNORECASE).group(0))\n\nprint(re.sub(r"\\d+", "?", text))\n```\n\n```output\nADA\nAda: ?, Grace: ?\n```\n:::\n\n:::note\n- Use raw strings for regex patterns so backslashes are easier to read.\n- Use capturing groups when the point is extraction, not just matching.\n- `re.match` anchors at the start; `re.search` finds the first match anywhere.\n- `re.compile` saves work when the pattern runs more than once.\n- `re.sub` rewrites matches; flags like `re.IGNORECASE` change matching behavior without rewriting the pattern.\n- Reach for string methods before regex when the pattern is simple.\n:::\n', 'runtime-type-checks.md': '+++\nslug = "runtime-type-checks"\ntitle = "Runtime Type Checks"\nsection = "Types"\nsummary = "type, isinstance, and issubclass inspect runtime relationships."\ndoc_path = "/library/functions.html#isinstance"\nsee_also = [\n "type-hints",\n "protocols",\n "casts-and-any",\n "abstract-base-classes",\n]\n+++\n\nRuntime type checks inspect real objects while the program is running. They are different from type hints, which mostly guide tools before the program runs.\n\nUse `type()` when the exact class matters, `isinstance()` when subclasses should count, and `issubclass()` when checking class relationships. Most APIs prefer behavior over type checks, but runtime checks are useful at input boundaries.\n\nDo not turn every function into a wall of `isinstance()` calls. If the code only needs an object that can perform an operation, duck typing or a protocol may be clearer.\n\n:::program\n```python\nclass Animal:\n pass\n\nclass Dog(Animal):\n pass\n\npet = Dog()\n\nprint(type(pet).__name__)\nprint(type(pet) is Animal)\nprint(isinstance(pet, Animal))\nprint(issubclass(Dog, Animal))\n```\n:::\n\n:::cell\n`type()` reports the exact runtime class. A `Dog` instance is not exactly an `Animal` instance.\n\n```python\nclass Animal:\n pass\n\nclass Dog(Animal):\n pass\n\npet = Dog()\nprint(type(pet).__name__)\nprint(type(pet) is Animal)\n```\n\n```output\nDog\nFalse\n```\n:::\n\n:::cell\n`isinstance()` accepts subclasses, which is usually what API boundaries want.\n\n```python\nprint(isinstance(pet, Dog))\nprint(isinstance(pet, Animal))\n```\n\n```output\nTrue\nTrue\n```\n:::\n\n:::cell\n`issubclass()` checks class relationships rather than individual objects.\n\n```python\nprint(issubclass(Dog, Animal))\n```\n\n```output\nTrue\n```\n:::\n\n:::note\n- `type()` is exact; `isinstance()` follows inheritance.\n- Runtime checks inspect objects, not static annotations.\n- Prefer behavior, protocols, or clear validation over scattered type checks.\n:::\n', 'scope-global-nonlocal.md': '+++\nslug = "scope-global-nonlocal"\ntitle = "Global and Nonlocal"\nsection = "Functions"\nsummary = "global and nonlocal choose which outer binding assignment should update."\ndoc_path = "/reference/simple_stmts.html#the-global-statement"\nsee_also = [\n "variables",\n "closures",\n "functions",\n]\n+++\n\nAssignment normally creates or updates a local name inside the current function. `global` and `nonlocal` are explicit escape hatches for rebinding names outside that local scope.\n\nUse `nonlocal` when an inner function should update a name in an enclosing function. Use `global` rarely; passing values and returning results is usually clearer.\n\nThese statements affect name binding, not object mutation. Mutating a shared list is different from rebinding the name itself.\n\n:::program\n```python\ncount = 0\n\ndef bump_global():\n global count\n count += 1\n\nbump_global()\nprint(count)\n\n\ndef make_counter():\n total = 0\n def bump():\n nonlocal total\n total += 1\n return total\n return bump\n\ncounter = make_counter()\nprint(counter())\nprint(counter())\n```\n:::\n\n:::cell\n`global` tells assignment to update a module-level binding. Without it, `count += 1` would try to assign a local `count`.\n\n```python\ncount = 0\n\ndef bump_global():\n global count\n count += 1\n\nbump_global()\nprint(count)\n```\n\n```output\n1\n```\n:::\n\n:::cell\n`nonlocal` tells assignment to update a binding in the nearest enclosing function scope. This is useful for small closures that keep state.\n\n```python\ndef make_counter():\n total = 0\n def bump():\n nonlocal total\n total += 1\n return total\n return bump\n\ncounter = make_counter()\nprint(counter())\nprint(counter())\n```\n\n```output\n1\n2\n```\n:::\n\n:::note\n- Assignment inside a function is local unless declared otherwise.\n- Prefer `nonlocal` for closure state and avoid `global` unless module state is truly intended.\n- Passing values and returning results is usually easier to test than rebinding outer names.\n:::\n', 'sentinel-iteration.md': '+++\nslug = "sentinel-iteration"\ntitle = "Sentinel Iteration"\nsection = "Iteration"\nsummary = "iter(callable, sentinel) repeats calls until a marker value appears."\ndoc_path = "/library/functions.html#iter"\n+++\n\n`iter(callable, sentinel)` keeps calling a zero-argument callable and yields each result until the callable returns the sentinel value. It is the right shape for repeated reads from files, sockets, or queues — sources where each call produces the next chunk and a known marker means "no more".\n\nReach for it instead of writing `while True:` plus a manual break when the loop body would do nothing else but read and check. The two-argument form turns a polling callable into something that composes with `for` loops, comprehensions, and other iterator helpers.\n\nThe callable must take no arguments. Wrap a parameterized reader in a small lambda or method that closes over the parameters when the underlying API needs them.\n\n:::program\n```python\nlines = iter(["alpha", "beta", ""])\n\ndef read_line():\n return next(lines)\n\nfor line in iter(read_line, ""):\n print(line.upper())\n```\n:::\n\n:::cell\nA zero-argument callable produces one value at a time.\n\n```python\nlines = iter(["alpha", "beta", ""])\n\ndef read_line():\n return next(lines)\n\nfor line in iter(read_line, ""):\n print(line.upper())\n```\n\n```output\nALPHA\nBETA\n```\n:::\n\n:::note\n- A zero-argument callable produces one value at a time.\n- The sentinel value stops the loop without appearing in the output.\n- This form is useful for repeated reads from files, sockets, or queues.\n:::\n', 'sets.md': '+++\nslug = "sets"\ntitle = "Sets"\nsection = "Collections"\nsummary = "Sets store unique values and make membership checks explicit."\ndoc_path = "/tutorial/datastructures.html#sets"\n+++\n\nSets store unique hashable values. Use them when membership and de-duplication matter more than order.\n\nA list can answer membership with `in`, but a set communicates that membership is the main operation. Set algebra then expresses how groups relate to each other.\n\nBecause sets are unordered, examples often wrap output in `sorted()` so the display is deterministic.\n\n:::program\n```python\nlanguages = ["python", "go", "python"]\nunique_languages = set(languages)\nprint(sorted(unique_languages))\n\nallowed = {"python", "rust"}\nprint("python" in allowed)\nprint("ruby" in allowed)\n\ncompiled = {"go", "rust"}\nprint(sorted(allowed | compiled))\nprint(sorted(allowed & compiled))\nprint(sorted(allowed - compiled))\n```\n:::\n\n:::cell\nCreating a set removes duplicates. Keep a list when order and repeated values matter; convert to a set when uniqueness is the point.\n\n```python\nlanguages = ["python", "go", "python"]\nunique_languages = set(languages)\nprint(sorted(unique_languages))\n```\n\n```output\n[\'go\', \'python\']\n```\n:::\n\n:::cell\nMembership checks are the everyday set operation. A list can also use `in`, but a set says that membership is central to the data shape.\n\n```python\nallowed = {"python", "rust"}\nprint("python" in allowed)\nprint("ruby" in allowed)\n```\n\n```output\nTrue\nFalse\n```\n:::\n\n:::cell\nUnion, intersection, and difference describe relationships between groups without manual loops.\n\n```python\ncompiled = {"go", "rust"}\nprint(sorted(allowed | compiled))\nprint(sorted(allowed & compiled))\nprint(sorted(allowed - compiled))\n```\n\n```output\n[\'go\', \'python\', \'rust\']\n[\'rust\']\n[\'python\']\n```\n:::\n\n:::note\n- Use lists when order and repeated values matter.\n- Use sets when uniqueness and membership are the main operations.\n- Prefer lists when order or repeated values are part of the meaning.\n- Sets are unordered, so sort them when examples need deterministic display.\n:::\n', 'slices.md': '+++\nslug = "slices"\ntitle = "Slices"\nsection = "Collections"\nsummary = "Slices copy meaningful ranges from ordered sequences."\ndoc_path = "/tutorial/introduction.html#lists"\n+++\n\nSlicing reads a range from an ordered sequence with `start:stop:step`. It exists because Python code often needs a meaningful piece of a sequence: a page, a prefix, a tail, a stride, or a reversed view.\n\nThe stop index is excluded. That convention makes lengths and adjacent ranges line up: `items[:3]` and `items[3:]` split a sequence without overlap.\n\nSlices return new sequence objects for built-in lists and strings. Use indexing for one item; use slicing when the result should still be a sequence.\n\n:::program\n```python\nletters = ["a", "b", "c", "d", "e", "f"]\nfirst_page = letters[:3]\nrest = letters[3:]\nprint(first_page)\nprint(rest)\n\nmiddle = letters[1:5]\nevery_other = letters[::2]\nreversed_letters = letters[::-1]\nprint(middle)\nprint(every_other)\nprint(reversed_letters)\nprint(letters)\n```\n:::\n\n:::cell\nOmitted bounds mean “from the beginning” or “through the end.” Because the stop index is excluded, adjacent slices split a sequence cleanly.\n\n```python\nletters = ["a", "b", "c", "d", "e", "f"]\nfirst_page = letters[:3]\nrest = letters[3:]\nprint(first_page)\nprint(rest)\n```\n\n```output\n[\'a\', \'b\', \'c\']\n[\'d\', \'e\', \'f\']\n```\n:::\n\n:::cell\nUse `start:stop` for a middle range and `step` when you want to skip or walk backward. These operations return new lists; the original list is unchanged.\n\n```python\nmiddle = letters[1:5]\nevery_other = letters[::2]\nreversed_letters = letters[::-1]\nprint(middle)\nprint(every_other)\nprint(reversed_letters)\nprint(letters)\n```\n\n```output\n[\'b\', \'c\', \'d\', \'e\']\n[\'a\', \'c\', \'e\']\n[\'f\', \'e\', \'d\', \'c\', \'b\', \'a\']\n[\'a\', \'b\', \'c\', \'d\', \'e\', \'f\']\n```\n:::\n\n:::note\n- Slice stop indexes are excluded, so adjacent ranges compose cleanly.\n- Omitted bounds mean the beginning or end of the sequence.\n- A negative step walks backward; `[::-1]` is a common reversed-copy idiom.\n:::\n', 'sorting.md': '+++\nslug = "sorting"\ntitle = "Sorting"\nsection = "Collections"\nsummary = "sorted returns a new ordered list and key functions choose the sort value."\ndoc_path = "/howto/sorting.html"\n+++\n\n`sorted()` accepts any iterable and returns a new list. The original collection is left untouched, which makes `sorted()` useful in expressions and pipelines.\n\nUse `key=` to say what value should be compared for each item. This is the idiomatic way to sort records, tuples, dictionaries, and objects by a field.\n\nUse `reverse=True` for descending order. Use `list.sort()` instead when you intentionally want to mutate an existing list in place.\n\n:::program\n```python\nnames = ["Guido", "Ada", "Grace"]\nprint(sorted(names))\nprint(names)\n\nusers = [\n {"name": "Ada", "score": 10},\n {"name": "Guido", "score": 8},\n {"name": "Grace", "score": 10},\n]\nranked = sorted(users, key=lambda user: user["score"], reverse=True)\nprint([user["name"] for user in ranked])\n\nusers.sort(key=lambda user: user["name"])\nprint([user["name"] for user in users])\n```\n:::\n\n:::cell\n`sorted()` returns a new list. Printing the original list afterward shows that the input order did not change.\n\n```python\nnames = ["Guido", "Ada", "Grace"]\nprint(sorted(names))\nprint(names)\n```\n\n```output\n[\'Ada\', \'Grace\', \'Guido\']\n[\'Guido\', \'Ada\', \'Grace\']\n```\n:::\n\n:::cell\nA key function computes the value to compare. Here the records are sorted by score, highest first, and the output shows the resulting order.\n\n```python\nusers = [\n {"name": "Ada", "score": 10},\n {"name": "Guido", "score": 8},\n {"name": "Grace", "score": 10},\n]\nranked = sorted(users, key=lambda user: user["score"], reverse=True)\nprint([user["name"] for user in ranked])\n```\n\n```output\n[\'Ada\', \'Grace\', \'Guido\']\n```\n:::\n\n:::cell\n`list.sort()` sorts the list in place. Use it when mutation is the point and no separate sorted copy is needed.\n\n```python\nusers.sort(key=lambda user: user["name"])\nprint([user["name"] for user in users])\n```\n\n```output\n[\'Ada\', \'Grace\', \'Guido\']\n```\n:::\n\n:::note\n- `sorted()` makes a new list; `list.sort()` mutates an existing list.\n- `key=` should return the value Python compares for each item.\n- Python\'s sort is stable, so equal keys keep their original relative order.\n:::\n', 'special-methods.md': '+++\nslug = "special-methods"\ntitle = "Special Methods"\nsection = "Data Model"\nsummary = "Special methods connect your objects to Python syntax and built-ins."\ndoc_path = "/reference/datamodel.html#special-method-names"\nsee_also = [\n "container-protocols",\n "operator-overloading",\n "callable-objects",\n "context-managers",\n]\n+++\n\nSpecial methods, often called dunder methods, connect user-defined classes to Python syntax and built-ins such as len(), iter(), and repr().\n\nImplementing these methods lets your objects participate in Python protocols rather than forcing callers to learn custom method names for common operations.\n\nGood special methods make objects feel boring in the best way: they work with the language features Python programmers already know.\n\n:::program\n```python\nclass Bag:\n def __init__(self, items):\n self.items = list(items)\n\n def __len__(self):\n return len(self.items)\n\n def __iter__(self):\n return iter(self.items)\n\n def __repr__(self):\n return f"Bag({self.items!r})"\n\n def __str__(self):\n return ", ".join(self.items)\n\n def __eq__(self, other):\n return isinstance(other, Bag) and self.items == other.items\n\n def __hash__(self):\n return hash(tuple(self.items))\n\n def __lt__(self, other):\n return len(self.items) < len(other.items)\n\n def __contains__(self, item):\n return item in self.items\n\n def __getitem__(self, index):\n return self.items[index]\n\n def __setitem__(self, index, value):\n self.items[index] = value\n\n def __bool__(self):\n return bool(self.items)\n\nbag = Bag(["a", "b"])\nprint(len(bag))\nprint(list(bag))\nprint(bag)\nprint(repr(bag))\nprint(Bag(["a", "b"]) == Bag(["a", "b"]))\nprint(Bag(["a"]) < Bag(["a", "b"]))\nprint(hash(Bag(["a"])) == hash(Bag(["a"])))\nprint("a" in bag)\nprint(bag[0])\nbag[1] = "z"\nprint(list(bag))\nprint(bool(Bag([])))\n\n\nclass Multiplier:\n def __init__(self, factor):\n self.factor = factor\n\n def __call__(self, value):\n return value * self.factor\n\ntriple = Multiplier(3)\nprint(triple(5))\n\n\nclass Trace:\n def __enter__(self):\n print("enter")\n return self\n\n def __exit__(self, *exc):\n print("exit")\n return False\n\nwith Trace():\n print("inside")\n```\n:::\n\n:::cell\nStart with a normal class that stores its data. Special methods build on ordinary instance state.\n\n```python\nclass Bag:\n def __init__(self, items):\n self.items = list(items)\n\nbag = Bag(["a", "b"])\nprint(bag.items)\n```\n\n```output\n[\'a\', \'b\']\n```\n:::\n\n:::cell\nImplement `__len__` to let `len()` ask the object for its size using Python\'s standard protocol.\n\n```python\nclass Bag:\n def __init__(self, items):\n self.items = list(items)\n\n def __len__(self):\n return len(self.items)\n\nbag = Bag(["a", "b"])\nprint(len(bag))\n```\n\n```output\n2\n```\n:::\n\n:::cell\nImplement `__iter__` to make the object iterable. Then tools such as `list()` can consume it without a custom method name.\n\n```python\nclass Bag:\n def __init__(self, items):\n self.items = list(items)\n\n def __len__(self):\n return len(self.items)\n\n def __iter__(self):\n return iter(self.items)\n\nbag = Bag(["a", "b"])\nprint(list(bag))\n```\n\n```output\n[\'a\', \'b\']\n```\n:::\n\n:::cell\nImplement `__repr__` to give the object a useful developer-facing representation when it is printed or inspected. With no `__str__` defined, `print()` falls back to `__repr__`.\n\n```python\nclass Bag:\n def __init__(self, items):\n self.items = list(items)\n\n def __len__(self):\n return len(self.items)\n\n def __iter__(self):\n return iter(self.items)\n\n def __repr__(self):\n return f"Bag({self.items!r})"\n\nbag = Bag(["a", "b"])\nprint(bag)\n```\n\n```output\nBag([\'a\', \'b\'])\n```\n:::\n\n:::cell\nAdd `__str__` for an end-user representation. `print()` and `str()` prefer `__str__`; `repr()` and the REPL still use `__repr__`. Keep `__repr__` unambiguous for debugging and let `__str__` be the friendly form.\n\n```python\nclass Bag:\n def __init__(self, items):\n self.items = list(items)\n\n def __repr__(self):\n return f"Bag({self.items!r})"\n\n def __str__(self):\n return ", ".join(self.items)\n\nbag = Bag(["a", "b"])\nprint(bag)\nprint(repr(bag))\n```\n\n```output\na, b\nBag([\'a\', \'b\'])\n```\n:::\n\n:::cell\n`__eq__` decides what equality means for the type. Defining `__eq__` removes the default `__hash__`, so add `__hash__` back when instances should work in sets or as dict keys. `__lt__` enables `<` and, with the rest of the order family, `sorted()`.\n\n```python\nclass Bag:\n def __init__(self, items):\n self.items = list(items)\n\n def __eq__(self, other):\n return isinstance(other, Bag) and self.items == other.items\n\n def __hash__(self):\n return hash(tuple(self.items))\n\n def __lt__(self, other):\n return len(self.items) < len(other.items)\n\nprint(Bag(["a", "b"]) == Bag(["a", "b"]))\nprint(Bag(["a"]) < Bag(["a", "b"]))\nprint(hash(Bag(["a"])) == hash(Bag(["a"])))\n```\n\n```output\nTrue\nTrue\nTrue\n```\n:::\n\n:::cell\nThe container protocols make instances behave like built-in containers. `__contains__` powers `in`, `__getitem__`/`__setitem__` power subscription, and `__bool__` decides truthiness for `if` and `while`. See [container-protocols](/data-model/container-protocols) for the full surface.\n\n```python\nclass Bag:\n def __init__(self, items):\n self.items = list(items)\n\n def __contains__(self, item):\n return item in self.items\n\n def __getitem__(self, index):\n return self.items[index]\n\n def __setitem__(self, index, value):\n self.items[index] = value\n\n def __bool__(self):\n return bool(self.items)\n\nbag = Bag(["a", "b"])\nprint("a" in bag)\nprint(bag[0])\nbag[1] = "z"\nprint(bag.items)\nprint(bool(Bag([])))\n```\n\n```output\nTrue\na\n[\'a\', \'z\']\nFalse\n```\n:::\n\n:::cell\n`__call__` makes an instance callable like a function — useful for stateful operations whose configuration deserves a name. `__enter__` and `__exit__` make a class a context manager so it can be used with `with`. The focused [callable-objects](/data-model/callable-objects) and [context-managers](/data-model/context-managers) pages go deeper.\n\n```python\nclass Multiplier:\n def __init__(self, factor):\n self.factor = factor\n\n def __call__(self, value):\n return value * self.factor\n\ntriple = Multiplier(3)\nprint(triple(5))\n\n\nclass Trace:\n def __enter__(self):\n print("enter")\n return self\n\n def __exit__(self, *exc):\n print("exit")\n return False\n\nwith Trace():\n print("inside")\n```\n\n```output\n15\nenter\ninside\nexit\n```\n:::\n\n:::note\n- Dunder methods are looked up by Python\'s data model protocols.\n- `__repr__` is the developer-facing form; `__str__` is the user-facing form. `print()` falls back to `__repr__` when `__str__` is missing.\n- Defining `__eq__` removes the default `__hash__`; restore it when the type should be hashable.\n- Container protocols (`__contains__`, `__getitem__`, `__setitem__`, `__bool__`) make instances behave like built-in containers.\n- `__call__` makes instances callable; `__enter__`/`__exit__` make them context managers.\n- Implement the smallest protocol that makes your object feel native.\n:::\n', 'string-formatting.md': '+++\nslug = "string-formatting"\ntitle = "String Formatting"\nsection = "Text"\nsummary = "f-strings turn values into readable text at the point of use."\ndoc_path = "/tutorial/inputoutput.html#formatted-string-literals"\n+++\n\nFormatted string literals, or f-strings, exist because programs constantly need to turn values into human-readable text. They keep the expression next to the words it explains.\n\nFormat specifications after `:` control presentation details such as width, alignment, padding, and precision. This separates the value being computed from the way it should be displayed.\n\nUse f-strings for most new formatting code. They relate directly to expressions: anything inside braces is evaluated, then formatted into the surrounding string.\n\n:::program\n```python\nname = "Ada"\nscore = 9.5\nrank = 1\n\nmessage = f"{name} scored {score}"\nprint(message)\n\nrow = f"{rank:>2} | {name:<8} | {score:05.1f}"\nprint(row)\n\nprint(f"{score = }")\n```\n:::\n\n:::cell\nAn f-string evaluates expressions inside braces and inserts their string form into the surrounding text. This is clearer than joining several converted values by hand.\n\n```python\nname = "Ada"\nscore = 9.5\nrank = 1\n\nmessage = f"{name} scored {score}"\nprint(message)\n```\n\n```output\nAda scored 9.5\n```\n:::\n\n:::cell\nFormat specifications after `:` control display without changing the underlying values. Here the rank is right-aligned, the name is left-aligned, and the score is padded to one decimal place.\n\n```python\nrow = f"{rank:>2} | {name:<8} | {score:05.1f}"\nprint(row)\n```\n\n```output\n 1 | Ada | 009.5\n```\n:::\n\n:::cell\nThe debug form with `=` is useful while learning or logging because it prints both the expression and the value.\n\n```python\nprint(f"{score = }")\n```\n\n```output\nscore = 9.5\n```\n:::\n\n:::note\n- Use `f"..."` strings for most new formatting code.\n- Expressions inside braces are evaluated before formatting.\n- Format specifications after `:` control alignment, width, padding, and precision.\n:::\n', 'strings.md': '+++\nslug = "strings"\ntitle = "Strings"\nsection = "Text"\nsummary = "Strings are immutable Unicode text sequences."\ndoc_path = "/library/stdtypes.html#text-sequence-type-str"\n+++\n\nPython strings are immutable Unicode text sequences. A `str` stores text as Unicode code points, so it can represent English, Thai, accented letters, emoji, and ordinary ASCII with the same type.\n\nUnicode matters because text length and byte length are different questions. The English word `"hello"` uses five code points and five UTF-8 bytes because ASCII characters encode as one byte each. The Thai greeting `"สวัสดี"` has six code points but needs eighteen UTF-8 bytes.\n\nUse `str` when you mean text, and encode to `bytes` only at boundaries such as files, network protocols, and binary APIs. String operations such as `upper()` and `strip()` return new strings instead of changing the original.\n\n:::program\n```python\nenglish = "hello"\nthai = "สวัสดี"\n\nfor label, word in [("English", english), ("Thai", thai)]:\n print(label, word, len(word), len(word.encode("utf-8")))\n\nprint(thai[0])\nprint([hex(ord(char)) for char in thai[:2]])\n\ntext = " café "\nclean = text.strip()\nprint(clean)\nprint(clean.upper())\nprint(clean.encode("utf-8"))\n```\n:::\n\n:::cell\nCompare an English greeting with a Thai greeting. Both are Python `str` values, but UTF-8 uses one byte for each ASCII code point and multiple bytes for many non-ASCII code points.\n\n```python\nenglish = "hello"\nthai = "สวัสดี"\n\nfor label, word in [("English", english), ("Thai", thai)]:\n print(label, word, len(word), len(word.encode("utf-8")))\n```\n\n```output\nEnglish hello 5 5\nThai สวัสดี 6 18\n```\n:::\n\n:::cell\nIndexing and iteration work with Unicode code points, not encoded bytes. `ord()` returns the integer code point, which is often displayed in hexadecimal when teaching text encoding.\n\n```python\nprint(thai[0])\nprint([hex(ord(char)) for char in thai[:2]])\n```\n\n```output\nส\n[\'0xe2a\', \'0xe27\']\n```\n:::\n\n:::cell\nString methods return new strings because strings are immutable. Encoding turns text into bytes when another system needs a byte representation.\n\n```python\ntext = " café "\nclean = text.strip()\nprint(clean)\nprint(clean.upper())\nprint(clean.encode("utf-8"))\n```\n\n```output\ncafé\nCAFÉ\nb\'caf\\xc3\\xa9\'\n```\n:::\n\n:::note\n- Use `str` for text and `bytes` for binary data.\n- `len(text)` counts Unicode code points; `len(text.encode("utf-8"))` counts encoded bytes.\n- ASCII text is a useful baseline because each ASCII code point is one UTF-8 byte.\n- String methods return new strings because strings are immutable.\n- User-visible “characters” can be more subtle than code points; combining marks and emoji sequences may need specialized text handling.\n:::\n', 'structured-data-shapes.md': '+++\nslug = "structured-data-shapes"\ntitle = "Structured Data Shapes"\nsection = "Classes"\nsummary = "dataclass, NamedTuple, and TypedDict each model records with different trade-offs."\ndoc_path = "/library/dataclasses.html"\nsee_also = [\n "dataclasses",\n "typed-dicts",\n "tuples",\n "classes",\n]\n+++\n\n`@dataclass`, `typing.NamedTuple`, and `typing.TypedDict` are three ways to give a record a name and a schema. They model the same data but differ in mutability, access syntax, and what the type information costs at runtime.\n\nA dataclass is a regular class with `__init__` and `__repr__` generated for you, so instances are mutable and attribute-accessed. A `NamedTuple` is a tuple subclass with named positions, so instances are immutable and support both `obj.field` and `obj[index]`. A `TypedDict` is a plain dict at runtime; the schema lives only in the type checker.\n\nPick the shape that matches the problem: a dataclass when methods or mutability help; a `NamedTuple` for small immutable records that benefit from unpacking; a `TypedDict` for JSON-shaped data that should stay as a dict at the boundary.\n\n:::program\n```python\nfrom dataclasses import dataclass\nfrom typing import NamedTuple, TypedDict\n\n@dataclass\nclass UserClass:\n name: str\n score: int\n\nclass UserTuple(NamedTuple):\n name: str\n score: int\n\nclass UserDict(TypedDict):\n name: str\n score: int\n\na = UserClass("Ada", 98)\nprint(a)\na.score = 100\nprint(a.score)\n\nb = UserTuple("Ada", 98)\nprint(b)\nprint(b.name, b[1])\nprint(b._replace(score=100))\n\nc: UserDict = {"name": "Ada", "score": 98}\nprint(c)\nprint(c["name"])\nprint(type(c).__name__)\n\nprint(isinstance(a, UserClass))\nprint(isinstance(b, tuple))\nprint(isinstance(c, dict))\n```\n:::\n\n:::cell\nA dataclass is a normal class with `__init__` and `__repr__` generated from the annotated fields. Instances are mutable, support attribute access, and can carry methods like any other class.\n\n```python\nfrom dataclasses import dataclass\n\n@dataclass\nclass UserClass:\n name: str\n score: int\n\na = UserClass("Ada", 98)\nprint(a)\na.score = 100\nprint(a.score)\n```\n\n```output\nUserClass(name=\'Ada\', score=98)\n100\n```\n:::\n\n:::cell\nA `NamedTuple` is a tuple subclass with named positions. Instances are immutable, support both `obj.field` and `obj[index]`, and the helper `_replace` produces a modified copy without mutating the original (since assigning to a field would fail).\n\n```python\nfrom typing import NamedTuple\n\nclass UserTuple(NamedTuple):\n name: str\n score: int\n\nb = UserTuple("Ada", 98)\nprint(b)\nprint(b.name, b[1])\nprint(b._replace(score=100))\n```\n\n```output\nUserTuple(name=\'Ada\', score=98)\nAda 98\nUserTuple(name=\'Ada\', score=100)\n```\n:::\n\n:::cell\nA `TypedDict` is a plain dictionary at runtime. The annotations exist only for the type checker, so the value behaves like any `dict` — useful for JSON-shaped data that crosses an API boundary as a mapping.\n\n```python\nfrom typing import TypedDict\n\nclass UserDict(TypedDict):\n name: str\n score: int\n\nc: UserDict = {"name": "Ada", "score": 98}\nprint(c)\nprint(c["name"])\nprint(type(c).__name__)\n```\n\n```output\n{\'name\': \'Ada\', \'score\': 98}\nAda\ndict\n```\n:::\n\n:::cell\nSame record, three runtime identities. The dataclass is its own class. The `NamedTuple` is literally a tuple. The `TypedDict` is literally a dict. That difference drives the choice: pick the form whose runtime behavior matches what the rest of the program already expects.\n\n```python\nprint(isinstance(a, UserClass))\nprint(isinstance(b, tuple))\nprint(isinstance(c, dict))\n```\n\n```output\nTrue\nTrue\nTrue\n```\n:::\n\n:::note\n- `@dataclass` — mutable, attribute access, methods; good default when behavior travels with data.\n- `typing.NamedTuple` — immutable, attribute + index access, tuple semantics; good for small records that flow through unpacking.\n- `typing.TypedDict` — runtime is `dict`, schema is type-checker-only; good for JSON-shaped data.\n- `collections.namedtuple` is the older, untyped form of `NamedTuple`; prefer the `typing` version in new code.\n:::\n', 'subprocesses.md': '+++\nslug = "subprocesses"\ntitle = "Subprocesses"\nsection = "Standard Library"\nsummary = "subprocess runs external commands with explicit arguments and captured outputs."\ndoc_path = "/library/subprocess.html"\nexpected_output = "child process\\n0\\n"\n+++\n\n`subprocess` is the standard boundary for running external commands. It starts another program, waits for it, and gives you a result object with the exit code and captured output.\n\nUse a list of arguments when possible, capture output when the parent program needs to inspect it, and treat a non-zero return code as a failure. The same ideas apply whether the child program is Python, Git, a compiler, or another command-line tool.\n\nThe important boundary is between Python objects and the operating system process table. Python prepares arguments and environment, then the child program runs independently and reports back through streams and an exit status.\n\n:::program\n```python\nimport subprocess\nimport sys\n\nresult = subprocess.run(\n [sys.executable, "-c", "print(\'child process\')"],\n text=True,\n capture_output=True,\n check=True,\n)\n\nprint(result.stdout.strip())\nprint(result.returncode)\n```\n:::\n\n:::unsupported\nDynamic Workers do not provide child processes.\n\n```python\nresult = subprocess.run(\n [sys.executable, "-c", "print(\'child process\')"],\n text=True,\n capture_output=True,\n check=True,\n)\n```\n:::\n\n:::cell\n`subprocess.run()` starts a child process and waits for it. `capture_output=True` stores the child\'s standard output and error streams on the result object.\n\n```python\nimport subprocess\nimport sys\n\nresult = subprocess.run(\n [sys.executable, "-c", "print(\'child process\')"],\n text=True,\n capture_output=True,\n check=True,\n)\n\nprint(result.stdout.strip())\nprint(result.returncode)\n```\n\n```output\nchild process\n0\n```\n:::\n\n:::note\n- Use a list of arguments instead of shell strings when possible.\n- Capture output when the parent program needs to inspect it.\n- `check=True` turns non-zero exits into exceptions.\n:::\n', 'testing.md': '+++\nslug = "testing"\ntitle = "Testing"\nsection = "Standard Library"\nsummary = "Tests make expected behavior executable and repeatable."\ndoc_path = "/library/unittest.html"\nsee_also = [\n "assertions",\n "exceptions",\n "modules",\n]\n+++\n\nTests turn expected behavior into code that can be run again. The useful unit is usually a small example of behavior with clear input, action, and assertion.\n\nPython\'s `unittest` library provides test cases, assertions, suites, and runners. Projects often use `pytest` for ergonomics, but the same idea remains: a test names behavior and fails when the behavior changes.\n\nA broad testing practice also includes fixtures, integration tests, property tests, and coverage. This example stays on the smallest standard-library loop: define behavior, assert the result, run the suite, inspect success.\n\n:::program\n```python\nimport io\nimport unittest\n\n\ndef add(left, right):\n return left + right\n\n\ndef divide(left, right):\n if right == 0:\n raise ZeroDivisionError("denominator is zero")\n return left / right\n\n\nclass AddTests(unittest.TestCase):\n def setUp(self):\n self.zero = 0\n\n def test_adds_numbers(self):\n self.assertEqual(add(self.zero + 2, 3), 5)\n\n def test_adds_empty_strings(self):\n self.assertEqual(add("", "py"), "py")\n\n def test_divide_by_zero_raises(self):\n with self.assertRaises(ZeroDivisionError):\n divide(1, 0)\n\nsuite = unittest.defaultTestLoader.loadTestsFromTestCase(AddTests)\nstream = io.StringIO()\nresult = unittest.TextTestRunner(stream=stream, verbosity=0).run(suite)\nprint(result.testsRun)\nprint(result.wasSuccessful())\n```\n:::\n\n:::cell\nA test starts with behavior small enough to name. The function can be ordinary code; the test supplies a representative input and expected result.\n\n```python\ndef add(left, right):\n return left + right\n\nprint(add(2, 3))\n```\n\n```output\n5\n```\n:::\n\n:::cell\n`unittest.TestCase` groups test methods. `setUp` runs before each test method to build per-test fixtures, `assertEqual` checks values, and `assertRaises` asserts that a block raises the expected exception type.\n\n```python\nimport unittest\n\n\ndef divide(left, right):\n if right == 0:\n raise ZeroDivisionError("denominator is zero")\n return left / right\n\n\nclass AddTests(unittest.TestCase):\n def setUp(self):\n self.zero = 0\n\n def test_adds_numbers(self):\n self.assertEqual(add(self.zero + 2, 3), 5)\n\n def test_adds_empty_strings(self):\n self.assertEqual(add("", "py"), "py")\n\n def test_divide_by_zero_raises(self):\n with self.assertRaises(ZeroDivisionError):\n divide(1, 0)\n\nprint([name for name in dir(AddTests) if name.startswith("test_")])\n```\n\n```output\n[\'test_adds_empty_strings\', \'test_adds_numbers\', \'test_divide_by_zero_raises\']\n```\n:::\n\n:::cell\nA runner executes the suite and records whether every assertion passed. Capturing the runner stream keeps this page\'s output deterministic.\n\n```python\nimport io\n\nsuite = unittest.defaultTestLoader.loadTestsFromTestCase(AddTests)\nstream = io.StringIO()\nresult = unittest.TextTestRunner(stream=stream, verbosity=0).run(suite)\nprint(result.testsRun)\nprint(result.wasSuccessful())\n```\n\n```output\n3\nTrue\n```\n:::\n\n:::note\n- Test method names should describe behavior, not implementation details.\n- A good unit test is deterministic and independent of test order.\n- Use broader integration tests when the behavior depends on several components working together.\n:::\n', 'threads-and-processes.md': '+++\nslug = "threads-and-processes"\ntitle = "Threads and Processes"\nsection = "Standard Library"\nsummary = "Threads share memory, while processes run in separate interpreters."\ndoc_path = "/library/concurrent.futures.html"\nexpected_output = "[1, 4, 9]\\nProcessPoolExecutor\\n"\n+++\n\nThreads and processes are two ways to run work outside the current control path. Threads are useful for overlapping I/O-shaped waits, while processes are useful when CPU-bound work needs separate interpreter processes.\n\nThis is different from `asyncio`: threads and processes run callables through executors, while `async` code cooperatively awaits coroutines. Choose the smallest concurrency model that matches the bottleneck.\n\nThe executor interface lets callers submit ordinary functions without committing the rest of the code to one scheduling strategy. That makes it easier to compare thread and process boundaries at the call site.\n\n:::program\n```python\nfrom concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor\n\n\ndef square(number):\n return number * number\n\nwith ThreadPoolExecutor(max_workers=2) as pool:\n print(list(pool.map(square, [1, 2, 3])))\n\nprint(ProcessPoolExecutor.__name__)\n```\n:::\n\n:::unsupported\nDynamic Workers do not provide native threads or child processes.\n\n```python\nwith ThreadPoolExecutor(max_workers=2) as pool:\n print(list(pool.map(square, [1, 2, 3])))\n\nwith ProcessPoolExecutor(max_workers=2) as pool:\n print(list(pool.map(pow, [4, 5], [2, 2])))\n```\n:::\n\n:::cell\nA thread pool runs ordinary callables while sharing memory with the current process. `map()` returns results in input order.\n\n```python\nfrom concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor\n\n\ndef square(number):\n return number * number\n\nwith ThreadPoolExecutor(max_workers=2) as pool:\n print(list(pool.map(square, [1, 2, 3])))\n```\n\n```output\n[1, 4, 9]\n```\n:::\n\n:::cell\nA process pool uses separate Python processes. That boundary is heavier, but it can run CPU-bound work outside the current interpreter.\n\n```python\nprint(ProcessPoolExecutor.__name__)\n```\n\n```output\nProcessPoolExecutor\n```\n:::\n\n:::note\n- Threads share memory, so mutable shared state needs care.\n- Processes avoid shared interpreter state but require values to cross a process boundary.\n- Prefer `asyncio` for coroutine-based I/O and executors for ordinary blocking callables.\n:::\n', 'truth-and-size.md': '+++\nslug = "truth-and-size"\ntitle = "Truth and Size"\nsection = "Data Model"\nsummary = "__bool__ and __len__ decide how objects behave in truth tests and len()."\ndoc_path = "/reference/datamodel.html#object.__bool__"\nsee_also = [\n "truthiness",\n "special-methods",\n "container-protocols",\n]\n+++\n\nTruth tests ask an object whether it should count as true. Containers usually answer through their size, while domain objects can answer with `__bool__` when emptiness is not the right idea.\n\n`__len__` supports `len(obj)` and also provides a fallback truth value: length zero is false, non-zero length is true. `__bool__` is more direct and wins when both are present.\n\nUse these methods to match the meaning of your object. A queue can be false when it has no items; an account might be true only when it is active, regardless of its balance.\n\n:::program\n```python\nclass Inbox:\n def __init__(self, messages):\n self.messages = list(messages)\n\n def __len__(self):\n return len(self.messages)\n\nclass Account:\n def __init__(self, active):\n self.active = active\n\n def __bool__(self):\n return self.active\n\nprint(len(Inbox(["hi", "bye"])))\nprint(bool(Inbox([])))\nprint(bool(Account(False)))\n```\n:::\n\n:::cell\n`__len__` lets `len()` ask an object for its size.\n\n```python\nclass Inbox:\n def __init__(self, messages):\n self.messages = list(messages)\n\n def __len__(self):\n return len(self.messages)\n\nprint(len(Inbox(["hi", "bye"])))\n```\n\n```output\n2\n```\n:::\n\n:::cell\nIf a class has `__len__` but no `__bool__`, Python uses zero length as false.\n\n```python\nprint(bool(Inbox([])))\n```\n\n```output\nFalse\n```\n:::\n\n:::cell\n`__bool__` expresses truth directly when the answer is not just container size.\n\n```python\nclass Account:\n def __init__(self, active):\n self.active = active\n\n def __bool__(self):\n return self.active\n\nprint(bool(Account(False)))\n```\n\n```output\nFalse\n```\n:::\n\n:::note\n- Prefer `__len__` for sized containers.\n- Prefer `__bool__` when truth has domain meaning.\n- Keep truth tests unsurprising; surprising falsy objects make conditionals harder to read.\n:::\n', 'truthiness.md': '+++\nslug = "truthiness"\ntitle = "Truthiness"\nsection = "Basics"\nsummary = "Python conditions use truthiness, not only explicit booleans."\ndoc_path = "/library/stdtypes.html#truth-value-testing"\n+++\n\nTruthiness is one of Python\'s most important conveniences: conditions can test objects directly instead of requiring explicit boolean comparisons everywhere.\n\nEmpty containers, numeric zero, None, and False are false; most other values are true. This makes common checks such as if items: concise and idiomatic.\n\nUse truthiness when it reads naturally, but choose explicit comparisons when the distinction matters, such as checking whether a value is exactly None.\n\n:::program\n```python\nitems = []\nname = "Ada"\n\nif not items:\n print("no items")\n\nif name:\n print("has a name")\n\nprint(bool(0))\nprint(bool(42))\n```\n:::\n\n:::cell\nTruthiness is one of Python\'s most important conveniences: conditions can test objects directly instead of requiring explicit boolean comparisons everywhere.\n\nEmpty containers, numeric zero, None, and False are false; most other values are true. This makes common checks such as if items: concise and idiomatic.\n\n```python\nitems = []\nname = "Ada"\n\nif not items:\n print("no items")\n```\n\n```output\nno items\n```\n:::\n\n:::cell\nUse truthiness when it reads naturally, but choose explicit comparisons when the distinction matters, such as checking whether a value is exactly None.\n\n```python\nif name:\n print("has a name")\n```\n\n```output\nhas a name\n```\n:::\n\n:::cell\nUse truthiness when it reads naturally, but choose explicit comparisons when the distinction matters, such as checking whether a value is exactly None.\n\n```python\nprint(bool(0))\nprint(bool(42))\n```\n\n```output\nFalse\nTrue\n```\n:::\n\n:::note\n- Empty containers and zero-like numbers are false in conditions.\n- Use explicit comparisons when they communicate intent better than truthiness.\n:::\n', 'tuples.md': '+++\nslug = "tuples"\ntitle = "Tuples"\nsection = "Collections"\nsummary = "Tuples group a fixed number of positional values."\ndoc_path = "/tutorial/datastructures.html#tuples-and-sequences"\nsee_also = [\n "lists",\n "unpacking",\n "structured-data-shapes",\n]\n+++\n\nTuples are ordered, immutable sequences. They exist for small fixed groups where position has meaning: coordinates, RGB colors, database rows, and multiple return values.\n\nUse lists for variable-length collections of similar items. Use tuples when the number of positions is part of the data shape and unpacking can give each position a useful name.\n\nBecause tuples are immutable, you cannot append or replace positions in place. If the shape needs to grow or change, a list or dataclass is usually a better fit.\n\n:::program\n```python\npoint = (3, 4)\nx, y = point\nprint(x + y)\n\nred = (255, 0, 0)\nprint(red[0])\nprint(len(red))\n\nrecord = ("Ada", 10)\nname, score = record\nprint(f"{name}: {score}")\n\nscores = [10, 9, 8]\nscores.append(7)\nprint(scores)\n\nstudent = ("Ada", 2024, "math")\nname, year, subject = student\nprint(name, year, subject)\n```\n:::\n\n:::cell\nUse a tuple for a fixed-size record where each position has a known meaning. Unpacking turns those positions into names at the point of use.\n\n```python\npoint = (3, 4)\nx, y = point\nprint(x + y)\n```\n\n```output\n7\n```\n:::\n\n:::cell\nTuples are sequences, so indexing and `len()` work. They are different from lists because their length and item references are fixed after creation.\n\n```python\nred = (255, 0, 0)\nprint(red[0])\nprint(len(red))\n```\n\n```output\n255\n3\n```\n:::\n\n:::cell\nTuples pair naturally with multiple return values and unpacking. If the fields need names everywhere, graduate to a dataclass or named tuple.\n\n```python\nrecord = ("Ada", 10)\nname, score = record\nprint(f"{name}: {score}")\n```\n\n```output\nAda: 10\n```\n:::\n\n:::cell\nLists and tuples carry different intent. A list holds a variable number of similar items and grows with `append`; a tuple has a fixed shape where each position has its own meaning, and unpacking gives those positions names.\n\n```python\nscores = [10, 9, 8]\nscores.append(7)\nprint(scores)\n\nstudent = ("Ada", 2024, "math")\nname, year, subject = student\nprint(name, year, subject)\n```\n\n```output\n[10, 9, 8, 7]\nAda 2024 math\n```\n:::\n\n:::note\n- Tuples are immutable sequences with fixed length.\n- Use tuples for small records where position has meaning.\n- Use lists for variable-length collections of similar items.\n- Reach for a dataclass or `NamedTuple` when fields deserve names everywhere they\'re used.\n:::\n', 'type-aliases.md': '+++\nslug = "type-aliases"\ntitle = "Type Aliases"\nsection = "Types"\nsummary = "Type aliases give a meaningful name to a repeated type shape."\ndoc_path = "/library/typing.html#type-aliases"\nsee_also = [\n "type-hints",\n "newtype",\n "union-and-optional-types",\n]\n+++\n\nA type alias gives a name to an annotation shape. It helps readers and type checkers understand the role of a value without repeating a long type expression everywhere.\n\nPython 3.13 supports the `type` statement for explicit aliases. Older assignment-style aliases still appear in code, but the `type` statement makes the intent clear and creates a `TypeAliasType` object at runtime.\n\nAn alias does not create a new runtime type. If you need a static distinction between compatible values such as user IDs and order IDs, use `NewType` instead.\n\n:::program\n```python\ntype UserId = int\ntype Scores = dict[UserId, int]\nLegacyName = str\n\n\ndef best_user(scores: Scores) -> UserId:\n return max(scores, key=scores.get)\n\nscores: Scores = {1: 98, 2: 91}\nprint(best_user(scores))\nprint(UserId.__name__)\nprint(LegacyName("Ada"))\n```\n:::\n\n:::cell\nThe `type` statement names an annotation shape. Here `Scores` means a dictionary from user IDs to integer scores.\n\n```python\ntype UserId = int\ntype Scores = dict[UserId, int]\n\n\ndef best_user(scores: Scores) -> UserId:\n return max(scores, key=scores.get)\n\nscores: Scores = {1: 98, 2: 91}\nprint(best_user(scores))\n```\n\n```output\n1\n```\n:::\n\n:::cell\nModern aliases are runtime objects that keep their alias name for introspection.\n\n```python\nprint(UserId.__name__)\nprint(Scores.__name__)\n```\n\n```output\nUserId\nScores\n```\n:::\n\n:::cell\nAssignment-style aliases are still common, but they are just ordinary names bound to existing objects.\n\n```python\nLegacyName = str\nprint(LegacyName("Ada"))\nprint(LegacyName is str)\n```\n\n```output\nAda\nTrue\n```\n:::\n\n:::note\n- Use aliases to name repeated or domain-specific annotation shapes.\n- A type alias does not validate values at runtime.\n- Use `NewType` when two values share a runtime representation but should not be mixed statically.\n:::\n', 'type-hints.md': '+++\nslug = "type-hints"\ntitle = "Type Hints"\nsection = "Types"\nsummary = "Annotations document expected types and power static analysis."\ndoc_path = "/library/typing.html"\nsee_also = [\n "union-and-optional-types",\n "type-aliases",\n "generics-and-typevar",\n "runtime-type-checks",\n]\n+++\n\nType hints are annotations that document expected shapes for values, parameters, and return results. They exist so tools and readers can understand API boundaries before the program runs.\n\nPython stores many annotations but does not enforce most of them at runtime. Use type hints for communication and static analysis; use validation or exceptions when runtime checks are required.\n\nThe alternative to an annotation is prose, tests, or runtime validation. Good Python code often uses all three at important boundaries.\n\n:::program\n```python\nfrom typing import TypeAlias\n\ndef total(numbers: list[int]) -> int:\n return sum(numbers)\n\nprint(total([1, 2, 3]))\nprint(total.__annotations__)\n\n\ndef label(score: int) -> str:\n return f"score={score}"\n\nprint(label("high"))\n\n\ndef find(name: str, options: list[str]) -> str | None:\n return name if name in options else None\n\nprint(find("Ada", ["Ada", "Grace"]))\nprint(find("Guido", ["Ada", "Grace"]))\n\n\nfrom typing import Optional\n\ndef lookup(name: str) -> Optional[int]:\n table = {"Ada": 1815, "Grace": 1906}\n return table.get(name)\n\nprint(lookup("Ada"))\nprint(lookup("Guido"))\n\n\nScore: TypeAlias = int\n\ndef grade(score: Score) -> str:\n return "pass" if score >= 50 else "fail"\n\nprint(grade(72))\n```\n:::\n\n:::cell\nType hints document expected parameter and return shapes. Python still runs the function normally at runtime.\n\n```python\ndef total(numbers: list[int]) -> int:\n return sum(numbers)\n\nprint(total([1, 2, 3]))\n```\n\n```output\n6\n```\n:::\n\n:::cell\nPython stores annotations on the function object for tools and introspection. Type checkers use this information without changing the function call syntax.\n\n```python\nprint(total.__annotations__)\n```\n\n```output\n{\'numbers\': list[int], \'return\': }\n```\n:::\n\n:::cell\nMost hints are not runtime validation. This call passes a string where the hint says `int`; Python still calls the function because the body can format any value.\n\n```python\ndef label(score: int) -> str:\n return f"score={score}"\n\nprint(label("high"))\n```\n\n```output\nscore=high\n```\n:::\n\n:::cell\nUse `X | Y` (PEP 604) to express "either type". `str | None` says the result is a string or absent. `typing.Optional[X]` is the older, still-supported spelling for the same idea — `Optional[X]` is equivalent to `X | None`.\n\n```python\ndef find(name: str, options: list[str]) -> str | None:\n return name if name in options else None\n\nprint(find("Ada", ["Ada", "Grace"]))\nprint(find("Guido", ["Ada", "Grace"]))\n\n\nfrom typing import Optional\n\ndef lookup(name: str) -> Optional[int]:\n table = {"Ada": 1815, "Grace": 1906}\n return table.get(name)\n\nprint(lookup("Ada"))\nprint(lookup("Guido"))\n```\n\n```output\nAda\nNone\n1815\nNone\n```\n:::\n\n:::cell\n`TypeAlias` names a type so it can be reused with intent. `Score: TypeAlias = int` keeps the underlying type at runtime but lets the API talk about a domain concept rather than a primitive.\n\n```python\nfrom typing import TypeAlias\n\nScore: TypeAlias = int\n\ndef grade(score: Score) -> str:\n return "pass" if score >= 50 else "fail"\n\nprint(grade(72))\n```\n\n```output\npass\n```\n:::\n\n:::note\n- Python does not enforce most type hints at runtime.\n- Tools like type checkers and editors use annotations to catch mistakes earlier.\n- Use `X | Y` for unions and `Optional[X]` for "X or None"; both spellings mean the same thing.\n- Reach for `TypeAlias` when a domain name reads better than a raw primitive type.\n- Use runtime validation when untrusted input must be rejected while the program runs.\n:::\n', 'typed-dicts.md': '+++\nslug = "typed-dicts"\ntitle = "TypedDict"\nsection = "Types"\nsummary = "TypedDict describes dictionaries with known string keys."\ndoc_path = "/library/typing.html#typing.TypedDict"\nsee_also = [\n "dicts",\n "json",\n "dataclasses",\n "structured-data-shapes",\n]\n+++\n\n`TypedDict` describes dictionary records with known keys. It is useful for JSON-like data that should remain a dictionary instead of becoming a class instance.\n\nThe important boundary is static versus runtime behavior. Type checkers can know that `name` is a string and `score` is an integer, but at runtime the value is still an ordinary `dict`.\n\nUse `TypedDict` for external records and `dataclass` when your own program wants attribute access, methods, and construction behavior.\n\n:::program\n```python\nfrom typing import NotRequired, TypedDict\n\nclass User(TypedDict):\n name: str\n score: int\n nickname: NotRequired[str]\n\n\ndef describe(user: User) -> str:\n return f"{user[\'name\']}: {user[\'score\']}"\n\nrecord: User = {"name": "Ada", "score": 98}\nprint(describe(record))\nprint(isinstance(record, dict))\nprint(record.get("nickname", "none"))\n```\n:::\n\n:::cell\nUse `TypedDict` for JSON-like records that remain dictionaries.\n\n```python\nfrom typing import TypedDict\n\nclass User(TypedDict):\n name: str\n score: int\n\n\ndef describe(user: User) -> str:\n return f"{user[\'name\']}: {user[\'score\']}"\n\nrecord: User = {"name": "Ada", "score": 98}\nprint(describe(record))\n```\n\n```output\nAda: 98\n```\n:::\n\n:::cell\nAt runtime, a `TypedDict` value is still a plain dictionary.\n\n```python\nprint(isinstance(record, dict))\nprint(type(record).__name__)\n```\n\n```output\nTrue\ndict\n```\n:::\n\n:::cell\n`NotRequired` marks a key that type checkers should treat as optional. Runtime lookup still uses normal dictionary tools such as `get()`.\n\n```python\nfrom typing import NotRequired\n\nclass UserWithNickname(TypedDict):\n name: str\n score: int\n nickname: NotRequired[str]\n\nrecord: UserWithNickname = {"name": "Ada", "score": 98}\nprint(record.get("nickname", "none"))\n```\n\n```output\nnone\n```\n:::\n\n:::note\n- Use `TypedDict` for dictionary records from JSON or APIs.\n- Type checkers understand required and optional keys.\n- Runtime behavior is still ordinary dictionary behavior.\n:::\n', 'union-and-optional-types.md': '+++\nslug = "union-and-optional-types"\ntitle = "Union and Optional Types"\nsection = "Types"\nsummary = "The | operator describes values that may have more than one static type."\ndoc_path = "/library/typing.html#typing.Optional"\nsee_also = [\n "none",\n "type-hints",\n "match-statements",\n]\n+++\n\nA union type says that a value may have one of several static shapes. `int | str` means callers may pass either an integer or a string.\n\n`T | None` is the modern spelling for an optional value. The annotation documents that absence is expected, but the code still needs to handle `None` before using the non-optional behavior.\n\nUnions are useful at boundaries where input is flexible. Inside a function, narrow the value with an `is None`, `isinstance()`, or pattern check so the rest of the code has one clear shape.\n\n:::program\n```python\ndef label(value: int | str) -> str:\n return f"item-{value}"\n\n\ndef greeting(name: str | None) -> str:\n if name is None:\n return "hello guest"\n return f"hello {name.upper()}"\n\nprint(label(3))\nprint(label("A"))\nprint(greeting(None))\nprint(greeting("Ada"))\nprint(greeting.__annotations__)\n```\n:::\n\n:::cell\nUse `A | B` when a value may have either type. The function body should use operations that make sense for every member of the union.\n\n```python\ndef label(value: int | str) -> str:\n return f"item-{value}"\n\nprint(label(3))\nprint(label("A"))\n```\n\n```output\nitem-3\nitem-A\n```\n:::\n\n:::cell\n`str | None` means the function accepts either a string or explicit absence. Check for `None` before calling string methods.\n\n```python\ndef greeting(name: str | None) -> str:\n if name is None:\n return "hello guest"\n return f"hello {name.upper()}"\n\nprint(greeting(None))\nprint(greeting("Ada"))\n```\n\n```output\nhello guest\nhello ADA\n```\n:::\n\n:::cell\nUnion annotations are visible at runtime, but Python does not enforce them when the function is called.\n\n```python\nprint(greeting.__annotations__)\n```\n\n```output\n{\'name\': str | None, \'return\': }\n```\n:::\n\n:::note\n- Use `A | B` when a value may have either type.\n- `T | None` means absence is an expected case, not an error by itself.\n- Narrow unions before using behavior that belongs to only one member type.\n:::\n', 'unpacking.md': '+++\nslug = "unpacking"\ntitle = "Unpacking"\nsection = "Collections"\nsummary = "Unpacking binds names from sequences and mappings concisely."\ndoc_path = "/tutorial/datastructures.html#tuples-and-sequences"\n+++\n\nUnpacking binds multiple names from one iterable or mapping. It makes the structure of data visible at the point where values are introduced.\n\nStarred unpacking handles variable-length sequences by collecting the middle or remaining values. This keeps common head-tail patterns readable.\n\nDictionary unpacking with ** connects structured data to function calls. It is widely used in configuration, adapters, and code that bridges APIs.\n\n:::program\n```python\npoint = (3, 4)\nx, y = point\nprint(x, y)\n\nfirst, *middle, last = [1, 2, 3, 4]\nprint(first, middle, last)\n\ndef describe(name, language):\n print(name, language)\n\ndata = {"name": "Ada", "language": "Python"}\ndescribe(**data)\n```\n:::\n\n:::cell\nUnpacking binds multiple names from one iterable or mapping. It makes the structure of data visible at the point where values are introduced.\n\n```python\npoint = (3, 4)\nx, y = point\nprint(x, y)\n```\n\n```output\n3 4\n```\n:::\n\n:::cell\nStarred unpacking handles variable-length sequences by collecting the middle or remaining values. This keeps common head-tail patterns readable.\n\n```python\nfirst, *middle, last = [1, 2, 3, 4]\nprint(first, middle, last)\n```\n\n```output\n1 [2, 3] 4\n```\n:::\n\n:::cell\nDictionary unpacking with ** connects structured data to function calls. It is widely used in configuration, adapters, and code that bridges APIs.\n\nDictionary unpacking with ** connects structured data to function calls. It is widely used in configuration, adapters, and code that bridges APIs.\n\n```python\ndef describe(name, language):\n print(name, language)\n\ndata = {"name": "Ada", "language": "Python"}\ndescribe(**data)\n```\n\n```output\nAda Python\n```\n:::\n\n:::note\n- Starred unpacking collects the remaining values into a list.\n- Dictionary unpacking with ** is common when calling functions with structured data.\n- Prefer indexing when you need one position; prefer unpacking when naming several positions makes the shape clearer.\n:::\n', 'values.md': '+++\nslug = "values"\ntitle = "Values"\nsection = "Basics"\nsummary = "Python programs evaluate expressions into objects such as text, numbers, booleans, and None."\ndoc_path = "/library/stdtypes.html"\n+++\n\nA Python program works by evaluating expressions into values. Values are objects: text, integers, floats, booleans, `None`, and many richer types introduced later.\n\nNames point to values; they are not declarations that permanently fix a type. Operations usually produce new values, which you can print, store, compare, or pass to functions.\n\nThis page is a map, not the whole territory. Later pages explain the boundaries: equality vs identity, mutable vs immutable values, truthiness vs literal booleans, and `None` vs a missing key or an exception.\n\n:::program\n```python\ntext = "python"\ncount = 3\nratio = 2.5\nready = True\nmissing = None\n\nprint(type(text).__name__)\nprint(text.upper())\nprint(count + 4)\nprint(ratio * 2)\n\nprint(ready and count > 0)\nprint(missing is None)\n```\n:::\n\n:::cell\nStart with several built-in values. Python does not require declarations before binding these names, and each value is still an object with a type.\n\n```python\ntext = "python"\ncount = 3\nratio = 2.5\nready = True\nmissing = None\n\nprint(type(text).__name__)\n```\n\n```output\nstr\n```\n:::\n\n:::cell\nMethods and operators evaluate to new values. The original `text`, `count`, and `ratio` bindings remain ordinary objects you can reuse.\n\n```python\nprint(text.upper())\nprint(count + 4)\nprint(ratio * 2)\n```\n\n```output\nPYTHON\n7\n5.0\n```\n:::\n\n:::cell\nBoolean expressions combine facts, and `None` is checked by identity because it is a singleton absence marker.\n\n```python\nprint(ready and count > 0)\nprint(missing is None)\n```\n\n```output\nTrue\nTrue\n```\n:::\n\n:::note\n- Values are objects; names point to them and operations usually create new values.\n- Use `is None` for the absence marker, not `== None`.\n- This overview introduces boundaries that later pages explain in detail.\n:::\n', 'variables.md': '+++\nslug = "variables"\ntitle = "Variables"\nsection = "Basics"\nsummary = "Names are bound to values with assignment."\ndoc_path = "/reference/simple_stmts.html#assignment-statements"\n+++\n\nPython variables are names bound to objects. Assignment creates or rebinds a name; it does not require a declaration and it does not permanently attach a type to the name.\n\nRebinding changes which object a name refers to. Augmented assignment such as `+=` is the idiomatic way to update counters and accumulators.\n\nUse clear names for values that matter later. Python\'s flexibility makes naming more important, not less.\n\nUse assignment when a value needs a name for reuse or explanation. Prefer a direct expression when naming the intermediate value would add noise.\n\n:::program\n```python\nmessage = "hi"\nprint(message)\n\nmessage = "hello"\nprint(message)\n\ncount = 3\ncount += 1\nprint(count)\n```\n:::\n\n:::cell\nAssignment binds a name to a value. Once bound, the name can be used anywhere that value is needed.\n\n```python\nmessage = "hi"\nprint(message)\n```\n\n```output\nhi\n```\n:::\n\n:::cell\nAssignment can rebind the same name to a different value. The name is not permanently attached to the first object.\n\n```python\nmessage = "hello"\nprint(message)\n```\n\n```output\nhello\n```\n:::\n\n:::cell\nAugmented assignment reads the current binding, computes an updated value, and stores the result back under the same name.\n\n```python\ncount = 3\ncount += 1\nprint(count)\n```\n\n```output\n4\n```\n:::\n\n:::note\n- Python variables are names bound to objects, not boxes with fixed types.\n- Rebinding a name is normal.\n- Use augmented assignment for counters and accumulators.\n:::\n', 'virtual-environments.md': '+++\nslug = "virtual-environments"\ntitle = "Virtual Environments"\nsection = "Modules"\nsummary = "Virtual environments isolate a project\'s Python packages."\ndoc_path = "/library/venv.html"\nexpected_output = ".venv\\nTrue\\n"\n+++\n\nVirtual environments isolate a project\'s Python packages. They exist so one project can install dependencies without changing another project\'s environment.\n\nThe usual command-line workflow is `python -m venv .venv`, but Python also exposes the same feature through the `venv` module. This example creates a temporary environment so the example cleans up after itself.\n\nA virtual environment changes where Python looks for installed packages. It does not change the language, and it is separate from package layout, imports, and module names.\n\n:::program\n```python\nimport pathlib\nimport tempfile\nimport venv\n\nwith tempfile.TemporaryDirectory() as directory:\n env_path = pathlib.Path(directory) / ".venv"\n builder = venv.EnvBuilder(with_pip=False)\n builder.create(env_path)\n\n print(env_path.name)\n print((env_path / "pyvenv.cfg").exists())\n```\n:::\n\n:::unsupported\nDynamic Workers do not provide the `venv` module or a project environment workflow.\n\n```python\nbuilder = venv.EnvBuilder(with_pip=False)\nbuilder.create(".venv")\n```\n:::\n\n:::cell\n`venv.EnvBuilder` creates the same kind of isolated environment as `python -m venv`. A temporary directory keeps the example from leaving project files behind.\n\n```python\nimport pathlib\nimport tempfile\nimport venv\n\nwith tempfile.TemporaryDirectory() as directory:\n env_path = pathlib.Path(directory) / ".venv"\n builder = venv.EnvBuilder(with_pip=False)\n builder.create(env_path)\n\n print(env_path.name)\n print((env_path / "pyvenv.cfg").exists())\n```\n\n```output\n.venv\nTrue\n```\n:::\n\n:::note\n- A virtual environment gives a project its own install location.\n- Inside a venv, `sys.prefix` usually differs from `sys.base_prefix`.\n- Use `python -m venv .venv` at the command line for everyday project setup.\n:::\n', 'warnings.md': '+++\nslug = "warnings"\ntitle = "Warnings"\nsection = "Errors"\nsummary = "warnings report soft problems without immediately stopping the program."\ndoc_path = "/library/warnings.html"\n+++\n\nwarnings report soft problems without immediately stopping the program. It exists to make a common boundary explicit instead of leaving the behavior implicit in a larger program.\n\nUse it when the problem shape matches the example, and prefer simpler neighboring tools when the extra machinery would hide the intent. The notes call out the boundary so the feature stays practical rather than decorative.\n\nThe example is small, deterministic, and focused on the semantic point. The complete source is editable below, while the walkthrough pairs the source with its output.\n\n:::program\n```python\nimport warnings\n\n\ndef old_name():\n warnings.warn("old_name is deprecated", DeprecationWarning, stacklevel=2)\n return "result"\n\nwarnings.simplefilter("always", DeprecationWarning)\nwith warnings.catch_warnings(record=True) as caught:\n print(old_name())\n print(caught[0].category.__name__)\n print(str(caught[0].message))\n```\n:::\n\n:::cell\nWarnings are useful for deprecations and soft failures.\n\n```python\nimport warnings\n\n\ndef old_name():\n warnings.warn("old_name is deprecated", DeprecationWarning, stacklevel=2)\n return "result"\n\nwarnings.simplefilter("always", DeprecationWarning)\nwith warnings.catch_warnings(record=True) as caught:\n print(old_name())\n print(caught[0].category.__name__)\n print(str(caught[0].message))\n```\n\n```output\nresult\nDeprecationWarning\nold_name is deprecated\n```\n:::\n\n:::note\n- Warnings are useful for deprecations and soft failures.\n- Filters decide whether warnings are ignored, shown, or turned into errors.\n- Tests often capture warnings to assert migration behavior.\n:::\n', 'while-loops.md': '+++\nslug = "while-loops"\ntitle = "While Loops"\nsection = "Control Flow"\nsummary = "while repeats until changing state makes a condition false."\ndoc_path = "/reference/compound_stmts.html#while"\n+++\n\nA `while` loop repeats while a condition remains true. Unlike `for`, which consumes an existing iterable, `while` is for state-driven repetition where the next step depends on what happened so far.\n\nThe loop body must make progress toward stopping. That progress might be decrementing a counter, reading until a sentinel value, or waiting until some external state changes.\n\nReach for `for` when you already have values to consume. Reach for `while` when the loop\'s own state decides whether another iteration is needed.\n\n:::program\n```python\nremaining = 3\nwhile remaining > 0:\n print(f"launch in {remaining}")\n remaining -= 1\nprint("liftoff")\n\nresponses = iter(["retry", "retry", "ok"])\nstatus = next(responses)\nwhile status != "ok":\n print(f"status: {status}")\n status = next(responses)\nprint(f"status: {status}")\n```\n:::\n\n:::cell\nUse `while` when the condition, not an iterable, controls repetition. Here the loop owns the countdown state and updates it each time through the body.\n\n```python\nremaining = 3\nwhile remaining > 0:\n print(f"launch in {remaining}")\n remaining -= 1\nprint("liftoff")\n```\n\n```output\nlaunch in 3\nlaunch in 2\nlaunch in 1\nliftoff\n```\n:::\n\n:::cell\nA sentinel loop stops when a special value appears. The loop does not know in advance how many retries it will need; it keeps going until the state says to stop.\n\n```python\nresponses = iter(["retry", "retry", "ok"])\nstatus = next(responses)\nwhile status != "ok":\n print(f"status: {status}")\n status = next(responses)\nprint(f"status: {status}")\n```\n\n```output\nstatus: retry\nstatus: retry\nstatus: ok\n```\n:::\n\n:::note\n- Use `while` when changing state decides whether the loop continues.\n- Update loop state inside the body so the condition can become false.\n- Prefer `for` when you already have a collection, range, iterator, or generator to consume.\n:::\n', 'yield-from.md': '+++\nslug = "yield-from"\ntitle = "Yield From"\nsection = "Iteration"\nsummary = "yield from delegates part of a generator to another iterable."\ndoc_path = "/reference/expressions.html#yield-expressions"\nsee_also = [\n "generators",\n "generator-expressions",\n "itertools",\n]\n+++\n\n`yield from` lets one generator yield every value from another iterable. It is a compact way to delegate part of a stream.\n\nUse it when a generator is mostly stitching together other iterables or sub-generators. It keeps the producer pipeline visible without writing a nested `for` loop.\n\nThe consumer still sees one stream of values.\n\n:::program\n```python\ndef page():\n yield "header"\n yield from ["intro", "body"]\n yield "footer"\n\nprint(list(page()))\n\n\ndef flatten(rows):\n for row in rows:\n yield from row\n\nprint(list(flatten([[1, 2], [3]])))\n```\n:::\n\n:::cell\n`yield from` delegates to another iterable. The caller receives one stream even though part of it came from a list.\n\n```python\ndef page():\n yield "header"\n yield from ["intro", "body"]\n yield "footer"\n\nprint(list(page()))\n```\n\n```output\n[\'header\', \'intro\', \'body\', \'footer\']\n```\n:::\n\n:::cell\nDelegation is useful when flattening nested iterables. `yield from row` replaces an inner loop that would yield each item by hand.\n\n```python\ndef flatten(rows):\n for row in rows:\n yield from row\n\nprint(list(flatten([[1, 2], [3]])))\n```\n\n```output\n[1, 2, 3]\n```\n:::\n\n:::note\n- `yield from iterable` yields each value from that iterable.\n- It keeps generator pipelines compact.\n- Use a plain `yield` when producing one value directly.\n:::\n'}
diff --git a/tests/fixtures/golden_examples.py b/tests/fixtures/golden_examples.py
index d649d1d..757400e 100644
--- a/tests/fixtures/golden_examples.py
+++ b/tests/fixtures/golden_examples.py
@@ -7,26 +7,65 @@
PYTHON_VERSION = '3.13'
REFERENCE_URL = 'https://docs.python.org/3.13/'
-EXAMPLES = [{'slug': 'hello-world',
- 'title': 'Hello World',
- 'section': 'Basics',
- 'summary': 'The first Python program prints a line of text.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/introduction.html',
+EXAMPLES = [{'cells': [{'code': 'print("hello world")',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': 'hello world',
+ 'prose': ['Every Python program starts by executing statements from top to bottom. '
+ 'Calling `print()` is the smallest useful program because it shows how '
+ 'Python evaluates an expression and sends text to standard output.']}],
'code': 'print("hello world")\n',
+ 'doc_path': '/tutorial/introduction.html',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/introduction.html',
'expected_output': 'hello world\n',
- 'notes': ['`print()` writes text followed by a newline.', 'Strings can be delimited with single or double quotes.'],
- 'cells': [{'prose': ['Every Python program starts by executing statements from top to bottom. Calling `print()` is '
- 'the smallest useful program because it shows how Python evaluates an expression and sends text '
- 'to standard output.'],
- 'code': 'print("hello world")',
- 'output': 'hello world',
- 'line': 17,
- 'kind': 'cell'}]},
- {'slug': 'values',
- 'title': 'Values',
+ 'explanation': ['Every Python program starts by executing statements from top to bottom. Calling '
+ '`print()` is the smallest useful program because it shows how Python evaluates '
+ 'an expression and sends text to standard output.',
+ 'Strings are ordinary values, so the message passed to `print()` can be changed, '
+ 'stored in a variable, or produced by a function. This example keeps the first '
+ 'program intentionally small.',
+ '`print()` writes text followed by a newline. Strings can be delimited with '
+ 'single or double quotes.'],
+ 'min_python': None,
+ 'notes': ['`print()` writes text followed by a newline.',
+ 'Strings can be delimited with single or double quotes.'],
'section': 'Basics',
- 'summary': 'Python programs evaluate expressions into objects such as text, numbers, booleans, and None.',
- 'doc_url': 'https://docs.python.org/3.13/library/stdtypes.html',
+ 'see_also': [],
+ 'slug': 'hello-world',
+ 'summary': 'The first Python program prints a line of text.',
+ 'title': 'Hello World',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'print("hello world")',
+ 'prose': 'Every Python program starts by executing statements from top to '
+ 'bottom. Calling `print()` is the smallest useful program because it '
+ 'shows how Python evaluates an expression and sends text to standard '
+ 'output.'}]},
+ {'cells': [{'code': 'text = "python"\n'
+ 'count = 3\n'
+ 'ratio = 2.5\n'
+ 'ready = True\n'
+ 'missing = None\n'
+ '\n'
+ 'print(type(text).__name__)',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': 'str',
+ 'prose': ['Start with several built-in values. Python does not require declarations '
+ 'before binding these names, and each value is still an object with a '
+ 'type.']},
+ {'code': 'print(text.upper())\nprint(count + 4)\nprint(ratio * 2)',
+ 'kind': 'cell',
+ 'line': 35,
+ 'output': 'PYTHON\n7\n5.0',
+ 'prose': ['Methods and operators evaluate to new values. The original `text`, '
+ '`count`, and `ratio` bindings remain ordinary objects you can reuse.']},
+ {'code': 'print(ready and count > 0)\nprint(missing is None)',
+ 'kind': 'cell',
+ 'line': 51,
+ 'output': 'True\nTrue',
+ 'prose': ['Boolean expressions combine facts, and `None` is checked by identity '
+ 'because it is a singleton absence marker.']}],
'code': 'text = "python"\n'
'count = 3\n'
'ratio = 2.5\n'
@@ -40,50 +79,128 @@
'\n'
'print(ready and count > 0)\n'
'print(missing is None)\n',
+ 'doc_path': '/library/stdtypes.html',
+ 'doc_url': 'https://docs.python.org/3.13/library/stdtypes.html',
'expected_output': 'str\nPYTHON\n7\n5.0\nTrue\nTrue\n',
+ 'explanation': ['A Python program works by evaluating expressions into values. Values are '
+ 'objects: text, integers, floats, booleans, `None`, and many richer types '
+ 'introduced later.',
+ 'Names point to values; they are not declarations that permanently fix a type. '
+ 'Operations usually produce new values, which you can print, store, compare, or '
+ 'pass to functions.',
+ 'This page is a map, not the whole territory. Later pages explain the '
+ 'boundaries: equality vs identity, mutable vs immutable values, truthiness vs '
+ 'literal booleans, and `None` vs a missing key or an exception.'],
+ 'min_python': None,
'notes': ['Values are objects; names point to them and operations usually create new values.',
'Use `is None` for the absence marker, not `== None`.',
'This overview introduces boundaries that later pages explain in detail.'],
- 'cells': [{'prose': ['Start with several built-in values. Python does not require declarations before binding these '
- 'names, and each value is still an object with a type.'],
- 'code': 'text = "python"\n'
- 'count = 3\n'
- 'ratio = 2.5\n'
- 'ready = True\n'
- 'missing = None\n'
- '\n'
- 'print(type(text).__name__)',
- 'output': 'str',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['Methods and operators evaluate to new values. The original `text`, `count`, and `ratio` '
- 'bindings remain ordinary objects you can reuse.'],
- 'code': 'print(text.upper())\nprint(count + 4)\nprint(ratio * 2)',
- 'output': 'PYTHON\n7\n5.0',
- 'line': 35,
- 'kind': 'cell'},
- {'prose': ['Boolean expressions combine facts, and `None` is checked by identity because it is a singleton '
- 'absence marker.'],
- 'code': 'print(ready and count > 0)\nprint(missing is None)',
- 'output': 'True\nTrue',
- 'line': 51,
- 'kind': 'cell'}]},
- {'slug': 'literals',
- 'title': 'Literals',
'section': 'Basics',
- 'summary': 'Literals write values directly in Python source code.',
- 'doc_url': 'https://docs.python.org/3.13/reference/lexical_analysis.html#literals',
+ 'see_also': [],
+ 'slug': 'values',
+ 'summary': 'Python programs evaluate expressions into objects such as text, numbers, booleans, '
+ 'and None.',
+ 'title': 'Values',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'text = "python"\n'
+ 'count = 3\n'
+ 'ratio = 2.5\n'
+ 'ready = True\n'
+ 'missing = None\n'
+ '\n'
+ 'print(type(text).__name__)',
+ 'prose': 'Start with several built-in values. Python does not require '
+ 'declarations before binding these names, and each value is still an '
+ 'object with a type.'},
+ {'code': 'print(text.upper())\nprint(count + 4)\nprint(ratio * 2)',
+ 'prose': 'Methods and operators evaluate to new values. The original `text`, '
+ '`count`, and `ratio` bindings remain ordinary objects you can reuse.'},
+ {'code': 'print(ready and count > 0)\nprint(missing is None)',
+ 'prose': 'Boolean expressions combine facts, and `None` is checked by identity '
+ 'because it is a singleton absence marker.'}]},
+ {'cells': [{'code': 'whole = 42\n'
+ 'fraction = 3.5\n'
+ 'complex_number = 2 + 3j\n'
+ 'print(whole, fraction, complex_number.imag)',
+ 'kind': 'cell',
+ 'line': 23,
+ 'output': '42 3.5 3.0',
+ 'prose': ['Numeric literals write numbers directly. Complex literals use `j` for the '
+ 'imaginary part.']},
+ {'code': 'flags = 0xFF\n'
+ 'mask = 0b1010\n'
+ 'million = 1_000_000\n'
+ 'print(flags, mask, million)',
+ 'kind': 'cell',
+ 'line': 38,
+ 'output': '255 10 1000000',
+ 'prose': ['Integer literals also accept hexadecimal (`0x`), binary (`0b`), and octal '
+ '(`0o`) prefixes. Underscores group digits visually without changing the '
+ 'value.']},
+ {'code': 'text = "python"\n'
+ 'raw_pattern = r"\\d+"\n'
+ 'data = b"py"\n'
+ 'score = 98\n'
+ 'formatted = f"score={score}"\n'
+ 'print(text)\n'
+ 'print(raw_pattern)\n'
+ 'print(data)\n'
+ 'print(formatted)',
+ 'kind': 'cell',
+ 'line': 53,
+ 'output': "python\n\\d+\nb'py'\nscore=98",
+ 'prose': ['String literals write Unicode text. Raw strings keep backslashes literal, '
+ 'bytes literals write binary data rather than text, and f-strings '
+ '(`f"..."`) embed expressions inline.']},
+ {'code': 'point = (2, 3)\n'
+ 'names = ["Ada", "Grace"]\n'
+ 'scores = {"Ada": 98}\n'
+ 'unique = {"py", "go"}\n'
+ 'print(point)\n'
+ 'print(names[0])\n'
+ 'print(scores["Ada"])\n'
+ 'print(sorted(unique))',
+ 'kind': 'cell',
+ 'line': 76,
+ 'output': "(2, 3)\nAda\n98\n['go', 'py']",
+ 'prose': ['Container literals create tuples, lists, dictionaries, and sets. Each '
+ 'container answers a different question about order, position, lookup, or '
+ 'uniqueness.']},
+ {'code': 'print(True, False, None)\nprint(...)',
+ 'kind': 'cell',
+ 'line': 98,
+ 'output': 'True False None\nEllipsis',
+ 'prose': ['`True`, `False`, `None`, and `...` are singleton literal-like constants '
+ 'used for truth values, absence, and placeholders.']},
+ {'code': 'print(type({}).__name__)\n'
+ 'print(type(set()).__name__)\n'
+ 'print(type({1, 2}).__name__)',
+ 'kind': 'cell',
+ 'line': 112,
+ 'output': 'dict\nset\nset',
+ 'prose': ['Curly-brace literals are dictionaries by default. The empty form `{}` is '
+ 'an empty dictionary, not an empty set; use `set()` for that. A non-empty '
+ '`{1, 2}` is a set because keyless items can only be a set.']}],
'code': 'whole = 42\n'
'fraction = 3.5\n'
'complex_number = 2 + 3j\n'
'print(whole, fraction, complex_number.imag)\n'
'\n'
+ 'flags = 0xFF\n'
+ 'mask = 0b1010\n'
+ 'million = 1_000_000\n'
+ 'print(flags, mask, million)\n'
+ '\n'
'text = "python"\n'
'raw_pattern = r"\\d+"\n'
'data = b"py"\n'
+ 'score = 98\n'
+ 'formatted = f"score={score}"\n'
'print(text)\n'
'print(raw_pattern)\n'
'print(data)\n'
+ 'print(formatted)\n'
'\n'
'point = (2, 3)\n'
'names = ["Ada", "Grace"]\n'
@@ -95,53 +212,135 @@
'print(sorted(unique))\n'
'\n'
'print(True, False, None)\n'
- 'print(...)\n',
- 'expected_output': "42 3.5 3.0\npython\n\\d+\nb'py'\n(2, 3)\nAda\n98\n['go', 'py']\nTrue False None\nEllipsis\n",
- 'notes': ['Literals are good for small local values; constants are better for repeated values with meaning.',
+ 'print(...)\n'
+ '\n'
+ 'print(type({}).__name__)\n'
+ 'print(type(set()).__name__)\n'
+ 'print(type({1, 2}).__name__)\n',
+ 'doc_path': '/reference/lexical_analysis.html#literals',
+ 'doc_url': 'https://docs.python.org/3.13/reference/lexical_analysis.html#literals',
+ 'expected_output': '42 3.5 3.0\n'
+ '255 10 1000000\n'
+ 'python\n'
+ '\\d+\n'
+ "b'py'\n"
+ 'score=98\n'
+ '(2, 3)\n'
+ 'Ada\n'
+ '98\n'
+ "['go', 'py']\n"
+ 'True False None\n'
+ 'Ellipsis\n'
+ 'dict\n'
+ 'set\n'
+ 'set\n',
+ 'explanation': ['Literals are source-code forms for values: numbers, text, bytes, containers, '
+ 'booleans, `None`, and a few specialized markers. They are how a program writes '
+ 'small values directly.',
+ 'The literal form is only the beginning. Later examples explain each value '
+ 'family in depth: strings are Unicode text, bytes are binary data, lists and '
+ 'dicts are containers, and `None` represents intentional absence.',
+ 'Use literals when the value is small and local. Give repeated or meaningful '
+ 'values a name so the program explains why that value matters.'],
+ 'min_python': None,
+ 'notes': ['Literals are good for small local values; constants are better for repeated values '
+ 'with meaning.',
'`{}` is an empty dictionary. Use `set()` for an empty set.',
'Bytes literals are binary data; string literals are Unicode text.',
'`...` evaluates to the `Ellipsis` object.'],
- 'cells': [{'prose': ['Numeric literals write numbers directly. Complex literals use `j` for the imaginary part.'],
- 'code': 'whole = 42\nfraction = 3.5\ncomplex_number = 2 + 3j\nprint(whole, fraction, complex_number.imag)',
- 'output': '42 3.5 3.0',
- 'line': 23,
- 'kind': 'cell'},
- {'prose': ['String literals write Unicode text. Raw strings keep backslashes literal, and bytes literals '
- 'write binary data rather than text.'],
- 'code': 'text = "python"\n'
- 'raw_pattern = r"\\d+"\n'
- 'data = b"py"\n'
- 'print(text)\n'
- 'print(raw_pattern)\n'
- 'print(data)',
- 'output': "python\n\\d+\nb'py'",
- 'line': 38,
- 'kind': 'cell'},
- {'prose': ['Container literals create tuples, lists, dictionaries, and sets. Each container answers a '
- 'different question about order, position, lookup, or uniqueness.'],
- 'code': 'point = (2, 3)\n'
- 'names = ["Ada", "Grace"]\n'
- 'scores = {"Ada": 98}\n'
- 'unique = {"py", "go"}\n'
- 'print(point)\n'
- 'print(names[0])\n'
- 'print(scores["Ada"])\n'
- 'print(sorted(unique))',
- 'output': "(2, 3)\nAda\n98\n['go', 'py']",
- 'line': 57,
- 'kind': 'cell'},
- {'prose': ['`True`, `False`, `None`, and `...` are singleton literal-like constants used for truth values, '
- 'absence, and placeholders.'],
- 'code': 'print(True, False, None)\nprint(...)',
- 'output': 'True False None\nEllipsis',
- 'line': 79,
- 'kind': 'cell'}]},
- {'slug': 'numbers',
- 'title': 'Numbers',
'section': 'Basics',
- 'summary': 'Python numbers include integers, floats, and complex values.',
- 'doc_url': 'https://docs.python.org/3.13/library/stdtypes.html#numeric-types-int-float-complex',
- 'code': 'count = 10\n'
+ 'see_also': ['values', 'strings', 'numbers', 'string-formatting'],
+ 'slug': 'literals',
+ 'summary': 'Literals write values directly in Python source code.',
+ 'title': 'Literals',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'whole = 42\n'
+ 'fraction = 3.5\n'
+ 'complex_number = 2 + 3j\n'
+ 'print(whole, fraction, complex_number.imag)',
+ 'prose': 'Numeric literals write numbers directly. Complex literals use `j` for '
+ 'the imaginary part.'},
+ {'code': 'flags = 0xFF\n'
+ 'mask = 0b1010\n'
+ 'million = 1_000_000\n'
+ 'print(flags, mask, million)',
+ 'prose': 'Integer literals also accept hexadecimal (`0x`), binary (`0b`), and '
+ 'octal (`0o`) prefixes. Underscores group digits visually without '
+ 'changing the value.'},
+ {'code': 'text = "python"\n'
+ 'raw_pattern = r"\\d+"\n'
+ 'data = b"py"\n'
+ 'score = 98\n'
+ 'formatted = f"score={score}"\n'
+ 'print(text)\n'
+ 'print(raw_pattern)\n'
+ 'print(data)\n'
+ 'print(formatted)',
+ 'prose': 'String literals write Unicode text. Raw strings keep backslashes '
+ 'literal, bytes literals write binary data rather than text, and '
+ 'f-strings (`f"..."`) embed expressions inline.'},
+ {'code': 'point = (2, 3)\n'
+ 'names = ["Ada", "Grace"]\n'
+ 'scores = {"Ada": 98}\n'
+ 'unique = {"py", "go"}\n'
+ 'print(point)\n'
+ 'print(names[0])\n'
+ 'print(scores["Ada"])\n'
+ 'print(sorted(unique))',
+ 'prose': 'Container literals create tuples, lists, dictionaries, and sets. Each '
+ 'container answers a different question about order, position, lookup, '
+ 'or uniqueness.'},
+ {'code': 'print(True, False, None)\nprint(...)',
+ 'prose': '`True`, `False`, `None`, and `...` are singleton literal-like '
+ 'constants used for truth values, absence, and placeholders.'},
+ {'code': 'print(type({}).__name__)\n'
+ 'print(type(set()).__name__)\n'
+ 'print(type({1, 2}).__name__)',
+ 'prose': 'Curly-brace literals are dictionaries by default. The empty form `{}` '
+ 'is an empty dictionary, not an empty set; use `set()` for that. A '
+ 'non-empty `{1, 2}` is a set because keyless items can only be a '
+ 'set.'}]},
+ {'cells': [{'code': 'count = 10\n'
+ 'ratio = 0.25\n'
+ '\n'
+ 'print(count + 5)\n'
+ 'print(count / 4)\n'
+ 'print(ratio * 2)',
+ 'kind': 'cell',
+ 'line': 21,
+ 'output': '15\n2.5\n0.5',
+ 'prose': ['Python has `int` for whole numbers and `float` for approximate real-valued '
+ 'arithmetic. True division with `/` returns a `float`, even when both '
+ 'inputs are integers.']},
+ {'code': 'print(count // 4)\nprint(count % 4)\nprint(2 ** 5)',
+ 'kind': 'cell',
+ 'line': 40,
+ 'output': '2\n2\n32',
+ 'prose': ['Floor division and modulo are useful when you need quotient and remainder '
+ 'behavior. Powers use `**`, not `^`.']},
+ {'code': 'z = 2 + 3j\nprint(z.real, z.imag)',
+ 'kind': 'cell',
+ 'line': 56,
+ 'output': '2.0 3.0',
+ 'prose': ['Complex numbers are built in. The literal suffix `j` marks the imaginary '
+ 'part.']},
+ {'code': 'import math\n'
+ '\n'
+ 'print(0.1 + 0.2)\n'
+ 'print(0.1 + 0.2 == 0.3)\n'
+ 'print(math.isclose(0.1 + 0.2, 0.3))\n'
+ 'print(round(3.14159, 2))',
+ 'kind': 'cell',
+ 'line': 69,
+ 'output': '0.30000000000000004\nFalse\nTrue\n3.14',
+ 'prose': ['Floating-point values are approximate, so `==` between expected and '
+ 'computed floats is rarely the right test. Compare with `math.isclose` (or '
+ 'work in `decimal.Decimal`) when the question is "are these the same number '
+ 'to within tolerance".']}],
+ 'code': 'import math\n'
+ '\n'
+ 'count = 10\n'
'ratio = 0.25\n'
'z = 2 + 3j\n'
'\n'
@@ -152,40 +351,95 @@
'print(2 ** 5)\n'
'print(z.real, z.imag)\n'
'print(0.1 + 0.2)\n'
+ 'print(0.1 + 0.2 == 0.3)\n'
+ 'print(math.isclose(0.1 + 0.2, 0.3))\n'
'print(round(3.14159, 2))\n',
- 'expected_output': '15\n2.5\n2\n2\n32\n2.0 3.0\n0.30000000000000004\n3.14\n',
+ 'doc_path': '/library/stdtypes.html#numeric-types-int-float-complex',
+ 'doc_url': 'https://docs.python.org/3.13/library/stdtypes.html#numeric-types-int-float-complex',
+ 'expected_output': '15\n2.5\n2\n2\n32\n2.0 3.0\n0.30000000000000004\nFalse\nTrue\n3.14\n',
+ 'explanation': ["Python's numeric model starts with `int`, `float`, and `complex`. Integers are "
+ 'arbitrary precision, floats are approximate double-precision values, and '
+ 'complex numbers carry real and imaginary parts.',
+ 'Operators encode different numeric questions. `/` means true division and '
+ 'returns a float, `//` means floor division, `%` gives the remainder, and `**` '
+ 'computes powers.',
+ 'Use rounding for display, not as a substitute for understanding floating-point '
+ 'approximation. Financial code usually needs `decimal.Decimal`, which is a '
+ 'separate precision topic.'],
+ 'min_python': None,
'notes': ["Python's `int` has arbitrary precision; it grows as large as memory allows.",
"Python's `float` is approximate double-precision floating point.",
'Use `/` for true division and `//` for floor division.',
- 'Use `decimal.Decimal` when decimal precision is the domain requirement, not just display polish.'],
- 'cells': [{'prose': ['Python has `int` for whole numbers and `float` for approximate real-valued arithmetic. True '
- 'division with `/` returns a `float`, even when both inputs are integers.'],
- 'code': 'count = 10\nratio = 0.25\n\nprint(count + 5)\nprint(count / 4)\nprint(ratio * 2)',
- 'output': '15\n2.5\n0.5',
- 'line': 21,
- 'kind': 'cell'},
- {'prose': ['Floor division and modulo are useful when you need quotient and remainder behavior. Powers use '
- '`**`, not `^`.'],
- 'code': 'print(count // 4)\nprint(count % 4)\nprint(2 ** 5)',
- 'output': '2\n2\n32',
- 'line': 40,
- 'kind': 'cell'},
- {'prose': ['Complex numbers are built in. The literal suffix `j` marks the imaginary part.'],
- 'code': 'z = 2 + 3j\nprint(z.real, z.imag)',
- 'output': '2.0 3.0',
- 'line': 56,
- 'kind': 'cell'},
- {'prose': ['Floating-point values are approximate. Round for display when the exact binary representation '
- 'is not the lesson.'],
- 'code': 'print(0.1 + 0.2)\nprint(round(3.14159, 2))',
- 'output': '0.30000000000000004\n3.14',
- 'line': 69,
- 'kind': 'cell'}]},
- {'slug': 'booleans',
- 'title': 'Booleans',
+ 'Use `math.isclose` instead of `==` for floating-point comparison; reach for '
+ '`decimal.Decimal` when exact decimal precision is the domain requirement.'],
'section': 'Basics',
- 'summary': 'Booleans represent truth values and combine with logical operators.',
- 'doc_url': 'https://docs.python.org/3.13/library/stdtypes.html#boolean-type-bool',
+ 'see_also': ['literals', 'operators'],
+ 'slug': 'numbers',
+ 'summary': 'Python numbers include integers, floats, and complex values.',
+ 'title': 'Numbers',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'count = 10\n'
+ 'ratio = 0.25\n'
+ '\n'
+ 'print(count + 5)\n'
+ 'print(count / 4)\n'
+ 'print(ratio * 2)',
+ 'prose': 'Python has `int` for whole numbers and `float` for approximate '
+ 'real-valued arithmetic. True division with `/` returns a `float`, '
+ 'even when both inputs are integers.'},
+ {'code': 'print(count // 4)\nprint(count % 4)\nprint(2 ** 5)',
+ 'prose': 'Floor division and modulo are useful when you need quotient and '
+ 'remainder behavior. Powers use `**`, not `^`.'},
+ {'code': 'z = 2 + 3j\nprint(z.real, z.imag)',
+ 'prose': 'Complex numbers are built in. The literal suffix `j` marks the '
+ 'imaginary part.'},
+ {'code': 'import math\n'
+ '\n'
+ 'print(0.1 + 0.2)\n'
+ 'print(0.1 + 0.2 == 0.3)\n'
+ 'print(math.isclose(0.1 + 0.2, 0.3))\n'
+ 'print(round(3.14159, 2))',
+ 'prose': 'Floating-point values are approximate, so `==` between expected and '
+ 'computed floats is rarely the right test. Compare with `math.isclose` '
+ '(or work in `decimal.Decimal`) when the question is "are these the '
+ 'same number to within tolerance".'}]},
+ {'cells': [{'code': 'logged_in = True\n'
+ 'has_permission = False\n'
+ '\n'
+ 'print(logged_in and has_permission)\n'
+ 'print(logged_in or has_permission)\n'
+ 'print(not has_permission)',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': 'False\nTrue\nTrue',
+ 'prose': ['Use booleans for facts that are either true or false. Python spells the '
+ 'constants `True` and `False`.',
+ 'Use `and`, `or`, and `not` to combine truth values. These operators read '
+ 'like English and short-circuit when possible.']},
+ {'code': 'name = "Ada"\nprint(name == "Ada" and len(name) > 0)',
+ 'kind': 'cell',
+ 'line': 38,
+ 'output': 'True',
+ 'prose': ['Comparisons produce booleans too, so they compose naturally with logical '
+ 'operators in conditions and validation checks.']},
+ {'code': 'print(isinstance(True, int))\n'
+ 'print(True + True)\n'
+ 'print(sum([True, True, False, True]))\n'
+ '\n'
+ 'def is_strict_int(value):\n'
+ ' return isinstance(value, int) and not isinstance(value, bool)\n'
+ '\n'
+ 'print(is_strict_int(True))\n'
+ 'print(is_strict_int(1))',
+ 'kind': 'cell',
+ 'line': 51,
+ 'output': 'True\n2\n3\nFalse\nTrue',
+ 'prose': ['`bool` is a subclass of `int`, which is occasionally a footgun. `True` '
+ 'behaves as `1` and `False` as `0` in arithmetic, and `isinstance(True, '
+ 'int)` is `True`. When a function must reject booleans, exclude them '
+ 'explicitly with `isinstance(value, int) and not isinstance(value, '
+ 'bool)`.']}],
'code': 'logged_in = True\n'
'has_permission = False\n'
'\n'
@@ -194,36 +448,137 @@
'print(not has_permission)\n'
'\n'
'name = "Ada"\n'
- 'print(name == "Ada" and len(name) > 0)\n',
- 'expected_output': 'False\nTrue\nTrue\nTrue\n',
+ 'print(name == "Ada" and len(name) > 0)\n'
+ '\n'
+ 'print(isinstance(True, int))\n'
+ 'print(True + True)\n'
+ 'print(sum([True, True, False, True]))\n'
+ '\n'
+ 'def is_strict_int(value):\n'
+ ' return isinstance(value, int) and not isinstance(value, bool)\n'
+ '\n'
+ 'print(is_strict_int(True))\n'
+ 'print(is_strict_int(1))\n',
+ 'doc_path': '/library/stdtypes.html#boolean-type-bool',
+ 'doc_url': 'https://docs.python.org/3.13/library/stdtypes.html#boolean-type-bool',
+ 'expected_output': 'False\nTrue\nTrue\nTrue\nTrue\n2\n3\nFalse\nTrue\n',
+ 'explanation': ['Booleans are the values `True` and `False`. They are produced by comparisons '
+ 'and combined with `and`, `or`, and `not`.',
+ "Python's logical operators short-circuit. That means the right side is "
+ 'evaluated only when needed, which keeps guard checks efficient and safe.',
+ 'Booleans are also connected to truthiness: many objects can be tested in '
+ 'conditions even when they are not literally `True` or `False`.'],
+ 'min_python': None,
'notes': ['Boolean constants are `True` and `False`, with capital letters.',
- '`and` and `or` short-circuit: Python does not evaluate the right side if the left side already determines '
- 'the result.',
- 'Prefer truthiness for containers and explicit comparisons when the exact boolean condition matters.'],
- 'cells': [{'prose': ['Use booleans for facts that are either true or false. Python spells the constants `True` and '
- '`False`.',
- 'Use `and`, `or`, and `not` to combine truth values. These operators read like English and '
- 'short-circuit when possible.'],
- 'code': 'logged_in = True\n'
- 'has_permission = False\n'
- '\n'
- 'print(logged_in and has_permission)\n'
- 'print(logged_in or has_permission)\n'
- 'print(not has_permission)',
- 'output': 'False\nTrue\nTrue',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['Comparisons produce booleans too, so they compose naturally with logical operators in '
- 'conditions and validation checks.'],
- 'code': 'name = "Ada"\nprint(name == "Ada" and len(name) > 0)',
- 'output': 'True',
- 'line': 38,
- 'kind': 'cell'}]},
- {'slug': 'operators',
- 'title': 'Operators',
+ '`and` and `or` short-circuit: Python does not evaluate the right side if the left '
+ 'side already determines the result.',
+ 'Prefer truthiness for containers and explicit comparisons when the exact boolean '
+ 'condition matters.',
+ '`bool` subclasses `int`; `isinstance(True, int)` is `True`. Exclude booleans '
+ 'explicitly when only "real" integers should pass.'],
'section': 'Basics',
- 'summary': 'Operators combine, compare, and test values in expressions.',
- 'doc_url': 'https://docs.python.org/3.13/reference/expressions.html#operator-precedence',
+ 'see_also': [],
+ 'slug': 'booleans',
+ 'summary': 'Booleans represent truth values and combine with logical operators.',
+ 'title': 'Booleans',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'logged_in = True\n'
+ 'has_permission = False\n'
+ '\n'
+ 'print(logged_in and has_permission)\n'
+ 'print(logged_in or has_permission)\n'
+ 'print(not has_permission)',
+ 'prose': 'Use booleans for facts that are either true or false. Python spells '
+ 'the constants `True` and `False`.'},
+ {'code': 'logged_in = True\n'
+ 'has_permission = False\n'
+ '\n'
+ 'print(logged_in and has_permission)\n'
+ 'print(logged_in or has_permission)\n'
+ 'print(not has_permission)',
+ 'prose': 'Use `and`, `or`, and `not` to combine truth values. These operators '
+ 'read like English and short-circuit when possible.'},
+ {'code': 'name = "Ada"\nprint(name == "Ada" and len(name) > 0)',
+ 'prose': 'Comparisons produce booleans too, so they compose naturally with '
+ 'logical operators in conditions and validation checks.'},
+ {'code': 'print(isinstance(True, int))\n'
+ 'print(True + True)\n'
+ 'print(sum([True, True, False, True]))\n'
+ '\n'
+ 'def is_strict_int(value):\n'
+ ' return isinstance(value, int) and not isinstance(value, bool)\n'
+ '\n'
+ 'print(is_strict_int(True))\n'
+ 'print(is_strict_int(1))',
+ 'prose': '`bool` is a subclass of `int`, which is occasionally a footgun. '
+ '`True` behaves as `1` and `False` as `0` in arithmetic, and '
+ '`isinstance(True, int)` is `True`. When a function must reject '
+ 'booleans, exclude them explicitly with `isinstance(value, int) and '
+ 'not isinstance(value, bool)`.'}]},
+ {'cells': [{'code': 'count = 10\n'
+ 'print(count + 5)\n'
+ 'print(count // 4)\n'
+ 'print(count % 4)\n'
+ 'print(2 ** 5)',
+ 'kind': 'cell',
+ 'line': 23,
+ 'output': '15\n2\n2\n32',
+ 'prose': ['Arithmetic operators compute new values. Use `//` for floor division, `%` '
+ 'for remainder, and `**` for powers.']},
+ {'code': 'score = 91\n'
+ 'print(80 <= score < 100)\n'
+ 'print(score == 100 or score >= 90)\n'
+ 'print("py" in "python")',
+ 'kind': 'cell',
+ 'line': 42,
+ 'output': 'True\nTrue\nTrue',
+ 'prose': ['Comparison operators produce booleans. Python comparisons can chain, which '
+ 'keeps range checks readable.']},
+ {'code': 'flags = 0b0011\n'
+ 'print(flags & 0b0101)\n'
+ 'print(flags | 0b0100)\n'
+ 'print(flags ^ 0b0101)\n'
+ 'print(flags << 1)',
+ 'kind': 'cell',
+ 'line': 59,
+ 'output': '1\n7\n6\n6',
+ 'prose': ['Bitwise operators work on integer bit patterns. They are useful for masks '
+ 'and flags, not ordinary boolean logic. `&` is bitwise AND, `|` is bitwise '
+ 'OR, `^` is exclusive OR, and `<<` shifts left.']},
+ {'code': 'class Scale:\n'
+ ' def __init__(self, value):\n'
+ ' self.value = value\n'
+ '\n'
+ ' def __matmul__(self, other):\n'
+ ' return self.value * other.value\n'
+ '\n'
+ 'print(Scale(2) @ Scale(3))',
+ 'kind': 'cell',
+ 'line': 78,
+ 'output': '6',
+ 'prose': ['The `@` operator is reserved for matrix-like multiplication and custom '
+ 'types that define `__matmul__`.']},
+ {'code': 'items = ["a", "b"]\nif (size := len(items)) > 0:\n print(size)',
+ 'kind': 'cell',
+ 'line': 97,
+ 'output': '2',
+ 'prose': ['The walrus operator `:=` assigns inside an expression. Use it when naming '
+ 'a value avoids repeating work in a condition.']},
+ {'code': 'def loud():\n'
+ ' print("ran")\n'
+ ' return True\n'
+ '\n'
+ 'print(False and loud())\n'
+ 'print(True or loud())\n'
+ 'print(True and loud())',
+ 'kind': 'cell',
+ 'line': 111,
+ 'output': 'False\nTrue\nran\nTrue',
+ 'prose': ['`and` and `or` short-circuit: the right side runs only when the left side '
+ 'cannot already determine the result. That makes them safe for guard '
+ 'expressions like `obj and obj.value` where the right side would fail on '
+ '`None`.']}],
'code': 'count = 10\n'
'print(count + 5)\n'
'print(count // 4)\n'
@@ -236,6 +591,8 @@
'print("py" in "python")\n'
'\n'
'flags = 0b0011\n'
+ 'print(flags & 0b0101)\n'
+ 'print(flags | 0b0100)\n'
'print(flags ^ 0b0101)\n'
'print(flags << 1)\n'
'\n'
@@ -251,57 +608,113 @@
'items = ["a", "b"]\n'
'if (size := len(items)) > 0:\n'
' print(size)\n',
- 'expected_output': '15\n2\n2\n32\nTrue\nTrue\nTrue\n6\n6\n6\n2\n',
- 'notes': ['Use the clearest operator for the question: arithmetic, comparison, boolean logic, membership, identity, '
- 'or bitwise manipulation.',
+ 'doc_path': '/reference/expressions.html#operator-precedence',
+ 'doc_url': 'https://docs.python.org/3.13/reference/expressions.html#operator-precedence',
+ 'expected_output': '15\n2\n2\n32\nTrue\nTrue\nTrue\n1\n7\n6\n6\n6\n2\n',
+ 'explanation': ['Operators are the punctuation and keywords that combine values into '
+ 'expressions. Some operators compute new values, some compare values, and some '
+ 'ask relationship questions such as membership or identity.',
+ 'This page is the surface map. Focused examples explain the deeper behavior of '
+ 'numbers, booleans, conditions, sets, assignment expressions, and operator '
+ 'overloading.',
+ 'Read operators by the question they ask: arithmetic computes, comparison '
+ 'answers true or false, boolean operators combine truth values, membership '
+ 'searches a container, and specialized operators should only appear when the '
+ 'data shape calls for them.'],
+ 'min_python': None,
+ 'notes': ['Use the clearest operator for the question: arithmetic, comparison, boolean logic, '
+ 'membership, identity, or bitwise manipulation.',
'`and` and `or` short-circuit, so the right side may not run.',
- 'Operators have precedence; use parentheses when grouping would otherwise be hard to read.',
+ 'Operators have precedence; use parentheses when grouping would otherwise be hard to '
+ 'read.',
'Custom operator behavior should make an object feel more natural, not more clever.'],
- 'cells': [{'prose': ['Arithmetic operators compute new values. Use `//` for floor division, `%` for remainder, and '
- '`**` for powers.'],
- 'code': 'count = 10\nprint(count + 5)\nprint(count // 4)\nprint(count % 4)\nprint(2 ** 5)',
- 'output': '15\n2\n2\n32',
- 'line': 23,
- 'kind': 'cell'},
- {'prose': ['Comparison operators produce booleans. Python comparisons can chain, which keeps range checks '
- 'readable.'],
- 'code': 'score = 91\n'
- 'print(80 <= score < 100)\n'
- 'print(score == 100 or score >= 90)\n'
- 'print("py" in "python")',
- 'output': 'True\nTrue\nTrue',
- 'line': 42,
- 'kind': 'cell'},
- {'prose': ['Bitwise operators work on integer bit patterns. They are useful for masks and flags, not '
- 'ordinary boolean logic.'],
- 'code': 'flags = 0b0011\nprint(flags ^ 0b0101)\nprint(flags << 1)',
- 'output': '6\n6',
- 'line': 59,
- 'kind': 'cell'},
- {'prose': ['The `@` operator is reserved for matrix-like multiplication and custom types that define '
- '`__matmul__`.'],
- 'code': 'class Scale:\n'
- ' def __init__(self, value):\n'
- ' self.value = value\n'
+ 'section': 'Basics',
+ 'see_also': ['numbers',
+ 'equality-and-identity',
+ 'assignment-expressions',
+ 'operator-overloading'],
+ 'slug': 'operators',
+ 'summary': 'Operators combine, compare, and test values in expressions.',
+ 'title': 'Operators',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'count = 10\n'
+ 'print(count + 5)\n'
+ 'print(count // 4)\n'
+ 'print(count % 4)\n'
+ 'print(2 ** 5)',
+ 'prose': 'Arithmetic operators compute new values. Use `//` for floor division, '
+ '`%` for remainder, and `**` for powers.'},
+ {'code': 'score = 91\n'
+ 'print(80 <= score < 100)\n'
+ 'print(score == 100 or score >= 90)\n'
+ 'print("py" in "python")',
+ 'prose': 'Comparison operators produce booleans. Python comparisons can chain, '
+ 'which keeps range checks readable.'},
+ {'code': 'flags = 0b0011\n'
+ 'print(flags & 0b0101)\n'
+ 'print(flags | 0b0100)\n'
+ 'print(flags ^ 0b0101)\n'
+ 'print(flags << 1)',
+ 'prose': 'Bitwise operators work on integer bit patterns. They are useful for '
+ 'masks and flags, not ordinary boolean logic. `&` is bitwise AND, `|` '
+ 'is bitwise OR, `^` is exclusive OR, and `<<` shifts left.'},
+ {'code': 'class Scale:\n'
+ ' def __init__(self, value):\n'
+ ' self.value = value\n'
+ '\n'
+ ' def __matmul__(self, other):\n'
+ ' return self.value * other.value\n'
+ '\n'
+ 'print(Scale(2) @ Scale(3))',
+ 'prose': 'The `@` operator is reserved for matrix-like multiplication and '
+ 'custom types that define `__matmul__`.'},
+ {'code': 'items = ["a", "b"]\nif (size := len(items)) > 0:\n print(size)',
+ 'prose': 'The walrus operator `:=` assigns inside an expression. Use it when '
+ 'naming a value avoids repeating work in a condition.'},
+ {'code': 'def loud():\n'
+ ' print("ran")\n'
+ ' return True\n'
+ '\n'
+ 'print(False and loud())\n'
+ 'print(True or loud())\n'
+ 'print(True and loud())',
+ 'prose': '`and` and `or` short-circuit: the right side runs only when the left '
+ 'side cannot already determine the result. That makes them safe for '
+ 'guard expressions like `obj and obj.value` where the right side would '
+ 'fail on `None`.'}]},
+ {'cells': [{'code': 'result = None\nprint(result is None)',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': 'True',
+ 'prose': ["`None` is Python's value for “nothing here.” Check it with `is None` "
+ 'because it is a singleton identity value.']},
+ {'code': 'def find_score(name):\n'
+ ' if name == "Ada":\n'
+ ' return 10\n'
+ ' return None\n'
'\n'
- ' def __matmul__(self, other):\n'
- ' return self.value * other.value\n'
+ 'score = find_score("Grace")\n'
+ 'print(score is None)',
+ 'kind': 'cell',
+ 'line': 30,
+ 'output': 'True',
+ 'prose': ['Functions often return `None` when absence is expected and callers can '
+ 'continue. The function name and surrounding code should make that '
+ 'possibility clear.']},
+ {'code': 'profile = {"name": "Ada"}\n'
+ 'print(profile.get("timezone", "UTC"))\n'
'\n'
- 'print(Scale(2) @ Scale(3))',
- 'output': '6',
- 'line': 74,
- 'kind': 'cell'},
- {'prose': ['The walrus operator `:=` assigns inside an expression. Use it when naming a value avoids '
- 'repeating work in a condition.'],
- 'code': 'items = ["a", "b"]\nif (size := len(items)) > 0:\n print(size)',
- 'output': '2',
- 'line': 93,
- 'kind': 'cell'}]},
- {'slug': 'none',
- 'title': 'None',
- 'section': 'Basics',
- 'summary': 'None represents expected absence, distinct from missing keys and errors.',
- 'doc_url': 'https://docs.python.org/3.13/library/constants.html#None',
+ 'try:\n'
+ ' int("python")\n'
+ 'except ValueError:\n'
+ ' print("invalid number")',
+ 'kind': 'cell',
+ 'line': 48,
+ 'output': 'UTC\ninvalid number',
+ 'prose': ['A missing dictionary key is another absence boundary. Use `get()` when the '
+ 'mapping can supply a default, and use exceptions for invalid operations '
+ 'that cannot produce a value.']}],
'code': 'result = None\n'
'print(result is None)\n'
'\n'
@@ -320,45 +733,69 @@
' int("python")\n'
'except ValueError:\n'
' print("invalid number")\n',
+ 'doc_path': '/library/constants.html#None',
+ 'doc_url': 'https://docs.python.org/3.13/library/constants.html#None',
'expected_output': 'True\nTrue\nUTC\ninvalid number\n',
+ 'explanation': ['`None` represents the absence of a value. It is the usual sentinel when a '
+ 'function has no result to return but the absence itself is meaningful.',
+ 'Because `None` is a singleton, idiomatic Python checks it with `is None` or `is '
+ 'not None`. This avoids confusing identity with value equality.',
+ 'Absence has several nearby shapes in Python. A function can return `None`, a '
+ 'dictionary lookup can supply a default for a missing key, and an invalid '
+ 'operation can raise an exception.'],
+ 'min_python': None,
'notes': ['Use `is None` rather than `== None`; `None` is a singleton identity value.',
'Use `None` for expected absence that callers can test.',
- 'Use dictionary defaults for missing mapping keys and exceptions for invalid operations.'],
- 'cells': [{'prose': ["`None` is Python's value for “nothing here.” Check it with `is None` because it is a singleton "
- 'identity value.'],
- 'code': 'result = None\nprint(result is None)',
- 'output': 'True',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['Functions often return `None` when absence is expected and callers can continue. The function '
- 'name and surrounding code should make that possibility clear.'],
- 'code': 'def find_score(name):\n'
- ' if name == "Ada":\n'
- ' return 10\n'
- ' return None\n'
- '\n'
- 'score = find_score("Grace")\n'
- 'print(score is None)',
- 'output': 'True',
- 'line': 30,
- 'kind': 'cell'},
- {'prose': ['A missing dictionary key is another absence boundary. Use `get()` when the mapping can supply '
- 'a default, and use exceptions for invalid operations that cannot produce a value.'],
- 'code': 'profile = {"name": "Ada"}\n'
- 'print(profile.get("timezone", "UTC"))\n'
- '\n'
- 'try:\n'
- ' int("python")\n'
- 'except ValueError:\n'
- ' print("invalid number")',
- 'output': 'UTC\ninvalid number',
- 'line': 48,
- 'kind': 'cell'}]},
- {'slug': 'variables',
- 'title': 'Variables',
+ 'Use dictionary defaults for missing mapping keys and exceptions for invalid '
+ 'operations.'],
'section': 'Basics',
- 'summary': 'Names are bound to values with assignment.',
- 'doc_url': 'https://docs.python.org/3.13/reference/simple_stmts.html#assignment-statements',
+ 'see_also': [],
+ 'slug': 'none',
+ 'summary': 'None represents expected absence, distinct from missing keys and errors.',
+ 'title': 'None',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'result = None\nprint(result is None)',
+ 'prose': "`None` is Python's value for “nothing here.” Check it with `is None` "
+ 'because it is a singleton identity value.'},
+ {'code': 'def find_score(name):\n'
+ ' if name == "Ada":\n'
+ ' return 10\n'
+ ' return None\n'
+ '\n'
+ 'score = find_score("Grace")\n'
+ 'print(score is None)',
+ 'prose': 'Functions often return `None` when absence is expected and callers '
+ 'can continue. The function name and surrounding code should make that '
+ 'possibility clear.'},
+ {'code': 'profile = {"name": "Ada"}\n'
+ 'print(profile.get("timezone", "UTC"))\n'
+ '\n'
+ 'try:\n'
+ ' int("python")\n'
+ 'except ValueError:\n'
+ ' print("invalid number")',
+ 'prose': 'A missing dictionary key is another absence boundary. Use `get()` '
+ 'when the mapping can supply a default, and use exceptions for invalid '
+ 'operations that cannot produce a value.'}]},
+ {'cells': [{'code': 'message = "hi"\nprint(message)',
+ 'kind': 'cell',
+ 'line': 19,
+ 'output': 'hi',
+ 'prose': ['Assignment binds a name to a value. Once bound, the name can be used '
+ 'anywhere that value is needed.']},
+ {'code': 'message = "hello"\nprint(message)',
+ 'kind': 'cell',
+ 'line': 32,
+ 'output': 'hello',
+ 'prose': ['Assignment can rebind the same name to a different value. The name is not '
+ 'permanently attached to the first object.']},
+ {'code': 'count = 3\ncount += 1\nprint(count)',
+ 'kind': 'cell',
+ 'line': 45,
+ 'output': '4',
+ 'prose': ['Augmented assignment reads the current binding, computes an updated value, '
+ 'and stores the result back under the same name.']}],
'code': 'message = "hi"\n'
'print(message)\n'
'\n'
@@ -368,33 +805,56 @@
'count = 3\n'
'count += 1\n'
'print(count)\n',
+ 'doc_path': '/reference/simple_stmts.html#assignment-statements',
+ 'doc_url': 'https://docs.python.org/3.13/reference/simple_stmts.html#assignment-statements',
'expected_output': 'hi\nhello\n4\n',
+ 'explanation': ['Python variables are names bound to objects. Assignment creates or rebinds a '
+ 'name; it does not require a declaration and it does not permanently attach a '
+ 'type to the name.',
+ 'Rebinding changes which object a name refers to. Augmented assignment such as '
+ '`+=` is the idiomatic way to update counters and accumulators.',
+ "Use clear names for values that matter later. Python's flexibility makes naming "
+ 'more important, not less.',
+ 'Use assignment when a value needs a name for reuse or explanation. Prefer a '
+ 'direct expression when naming the intermediate value would add noise.'],
+ 'min_python': None,
'notes': ['Python variables are names bound to objects, not boxes with fixed types.',
'Rebinding a name is normal.',
'Use augmented assignment for counters and accumulators.'],
- 'cells': [{'prose': ['Assignment binds a name to a value. Once bound, the name can be used anywhere that value is '
- 'needed.'],
- 'code': 'message = "hi"\nprint(message)',
- 'output': 'hi',
- 'line': 19,
- 'kind': 'cell'},
- {'prose': ['Assignment can rebind the same name to a different value. The name is not permanently attached '
- 'to the first object.'],
- 'code': 'message = "hello"\nprint(message)',
- 'output': 'hello',
- 'line': 32,
- 'kind': 'cell'},
- {'prose': ['Augmented assignment reads the current binding, computes an updated value, and stores the '
- 'result back under the same name.'],
- 'code': 'count = 3\ncount += 1\nprint(count)',
- 'output': '4',
- 'line': 45,
- 'kind': 'cell'}]},
- {'slug': 'constants',
- 'title': 'Constants',
'section': 'Basics',
- 'summary': 'Python uses naming conventions for values that should not change.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/classes.html#python-scopes-and-namespaces',
+ 'see_also': [],
+ 'slug': 'variables',
+ 'summary': 'Names are bound to values with assignment.',
+ 'title': 'Variables',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'message = "hi"\nprint(message)',
+ 'prose': 'Assignment binds a name to a value. Once bound, the name can be used '
+ 'anywhere that value is needed.'},
+ {'code': 'message = "hello"\nprint(message)',
+ 'prose': 'Assignment can rebind the same name to a different value. The name is '
+ 'not permanently attached to the first object.'},
+ {'code': 'count = 3\ncount += 1\nprint(count)',
+ 'prose': 'Augmented assignment reads the current binding, computes an updated '
+ 'value, and stores the result back under the same name.'}]},
+ {'cells': [{'code': 'MAX_RETRIES = 3\n'
+ 'API_VERSION = "2026-05"\n'
+ '\n'
+ 'for attempt in range(1, MAX_RETRIES + 1):\n'
+ ' print(f"attempt {attempt} of {MAX_RETRIES}")',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': 'attempt 1 of 3\nattempt 2 of 3\nattempt 3 of 3',
+ 'prose': ['Python does not have a `const` declaration like Go or Rust. Instead, '
+ 'modules use all-caps names for values callers should treat as fixed.',
+ 'The interpreter will still let you rebind the name, but the convention is '
+ 'strong enough that readers understand the design intent.']},
+ {'code': 'print(API_VERSION)',
+ 'kind': 'cell',
+ 'line': 37,
+ 'output': '2026-05',
+ 'prose': ['Constants are useful for configuration values that should be named once '
+ 'and reused instead of repeated as magic literals.']}],
'code': 'MAX_RETRIES = 3\n'
'API_VERSION = "2026-05"\n'
'\n'
@@ -402,33 +862,71 @@
' print(f"attempt {attempt} of {MAX_RETRIES}")\n'
'\n'
'print(API_VERSION)\n',
+ 'doc_path': '/tutorial/classes.html#python-scopes-and-namespaces',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/classes.html#python-scopes-and-namespaces',
'expected_output': 'attempt 1 of 3\nattempt 2 of 3\nattempt 3 of 3\n2026-05\n',
+ 'explanation': ['Python has no `const` keyword for ordinary variables. Instead, modules use '
+ 'all-caps names to mark values that should be treated as constants by '
+ 'convention.',
+ 'The interpreter will not stop rebinding, but the convention is important API '
+ 'communication. Readers understand that `MAX_RETRIES` is configuration, not loop '
+ 'state.',
+ 'Named constants remove magic values from code and give repeated literals one '
+ 'place to change.'],
+ 'min_python': None,
'notes': ['Python has no `const` keyword for ordinary names.',
- 'All-caps names such as `MAX_RETRIES` communicate that a value is intended to stay fixed.'],
- 'cells': [{'prose': ['Python does not have a `const` declaration like Go or Rust. Instead, modules use all-caps '
- 'names for values callers should treat as fixed.',
- 'The interpreter will still let you rebind the name, but the convention is strong enough that '
- 'readers understand the design intent.'],
- 'code': 'MAX_RETRIES = 3\n'
- 'API_VERSION = "2026-05"\n'
- '\n'
- 'for attempt in range(1, MAX_RETRIES + 1):\n'
- ' print(f"attempt {attempt} of {MAX_RETRIES}")',
- 'output': 'attempt 1 of 3\nattempt 2 of 3\nattempt 3 of 3',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['Constants are useful for configuration values that should be named once and reused instead of '
- 'repeated as magic literals.'],
- 'code': 'print(API_VERSION)',
- 'output': '2026-05',
- 'line': 37,
- 'kind': 'cell'}]},
- {'slug': 'truthiness',
- 'title': 'Truthiness',
+ 'All-caps names such as `MAX_RETRIES` communicate that a value is intended to stay '
+ 'fixed.'],
'section': 'Basics',
- 'summary': 'Python conditions use truthiness, not only explicit booleans.',
- 'doc_url': 'https://docs.python.org/3.13/library/stdtypes.html#truth-value-testing',
- 'code': 'items = []\n'
+ 'see_also': [],
+ 'slug': 'constants',
+ 'summary': 'Python uses naming conventions for values that should not change.',
+ 'title': 'Constants',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'MAX_RETRIES = 3\n'
+ 'API_VERSION = "2026-05"\n'
+ '\n'
+ 'for attempt in range(1, MAX_RETRIES + 1):\n'
+ ' print(f"attempt {attempt} of {MAX_RETRIES}")',
+ 'prose': 'Python does not have a `const` declaration like Go or Rust. Instead, '
+ 'modules use all-caps names for values callers should treat as fixed.'},
+ {'code': 'MAX_RETRIES = 3\n'
+ 'API_VERSION = "2026-05"\n'
+ '\n'
+ 'for attempt in range(1, MAX_RETRIES + 1):\n'
+ ' print(f"attempt {attempt} of {MAX_RETRIES}")',
+ 'prose': 'The interpreter will still let you rebind the name, but the '
+ 'convention is strong enough that readers understand the design '
+ 'intent.'},
+ {'code': 'print(API_VERSION)',
+ 'prose': 'Constants are useful for configuration values that should be named '
+ 'once and reused instead of repeated as magic literals.'}]},
+ {'cells': [{'code': 'items = []\nname = "Ada"\n\nif not items:\n print("no items")',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': 'no items',
+ 'prose': ["Truthiness is one of Python's most important conveniences: conditions can "
+ 'test objects directly instead of requiring explicit boolean comparisons '
+ 'everywhere.',
+ 'Empty containers, numeric zero, None, and False are false; most other '
+ 'values are true. This makes common checks such as if items: concise and '
+ 'idiomatic.']},
+ {'code': 'if name:\n print("has a name")',
+ 'kind': 'cell',
+ 'line': 35,
+ 'output': 'has a name',
+ 'prose': ['Use truthiness when it reads naturally, but choose explicit comparisons '
+ 'when the distinction matters, such as checking whether a value is exactly '
+ 'None.']},
+ {'code': 'print(bool(0))\nprint(bool(42))',
+ 'kind': 'cell',
+ 'line': 48,
+ 'output': 'False\nTrue',
+ 'prose': ['Use truthiness when it reads naturally, but choose explicit comparisons '
+ 'when the distinction matters, such as checking whether a value is exactly '
+ 'None.']}],
+ 'code': 'items = []\n'
'name = "Ada"\n'
'\n'
'if not items:\n'
@@ -439,34 +937,78 @@
'\n'
'print(bool(0))\n'
'print(bool(42))\n',
+ 'doc_path': '/library/stdtypes.html#truth-value-testing',
+ 'doc_url': 'https://docs.python.org/3.13/library/stdtypes.html#truth-value-testing',
'expected_output': 'no items\nhas a name\nFalse\nTrue\n',
+ 'explanation': ["Truthiness is one of Python's most important conveniences: conditions can test "
+ 'objects directly instead of requiring explicit boolean comparisons everywhere.',
+ 'Empty containers, numeric zero, None, and False are false; most other values '
+ 'are true. This makes common checks such as if items: concise and idiomatic.',
+ 'Use truthiness when it reads naturally, but choose explicit comparisons when '
+ 'the distinction matters, such as checking whether a value is exactly None.'],
+ 'min_python': None,
'notes': ['Empty containers and zero-like numbers are false in conditions.',
'Use explicit comparisons when they communicate intent better than truthiness.'],
- 'cells': [{'prose': ["Truthiness is one of Python's most important conveniences: conditions can test objects "
- 'directly instead of requiring explicit boolean comparisons everywhere.',
- 'Empty containers, numeric zero, None, and False are false; most other values are true. This '
- 'makes common checks such as if items: concise and idiomatic.'],
- 'code': 'items = []\nname = "Ada"\n\nif not items:\n print("no items")',
- 'output': 'no items',
+ 'section': 'Basics',
+ 'see_also': [],
+ 'slug': 'truthiness',
+ 'summary': 'Python conditions use truthiness, not only explicit booleans.',
+ 'title': 'Truthiness',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'items = []\nname = "Ada"\n\nif not items:\n print("no items")',
+ 'prose': "Truthiness is one of Python's most important conveniences: conditions "
+ 'can test objects directly instead of requiring explicit boolean '
+ 'comparisons everywhere.'},
+ {'code': 'items = []\nname = "Ada"\n\nif not items:\n print("no items")',
+ 'prose': 'Empty containers, numeric zero, None, and False are false; most other '
+ 'values are true. This makes common checks such as if items: concise '
+ 'and idiomatic.'},
+ {'code': 'if name:\n print("has a name")',
+ 'prose': 'Use truthiness when it reads naturally, but choose explicit '
+ 'comparisons when the distinction matters, such as checking whether a '
+ 'value is exactly None.'},
+ {'code': 'print(bool(0))\nprint(bool(42))',
+ 'prose': 'Use truthiness when it reads naturally, but choose explicit '
+ 'comparisons when the distinction matters, such as checking whether a '
+ 'value is exactly None.'}]},
+ {'cells': [{'code': 'left = [1, 2, 3]\n'
+ 'right = [1, 2, 3]\n'
+ 'print(left == right)\n'
+ 'print(left is right)',
+ 'kind': 'cell',
'line': 17,
- 'kind': 'cell'},
- {'prose': ['Use truthiness when it reads naturally, but choose explicit comparisons when the distinction '
- 'matters, such as checking whether a value is exactly None.'],
- 'code': 'if name:\n print("has a name")',
- 'output': 'has a name',
- 'line': 35,
- 'kind': 'cell'},
- {'prose': ['Use truthiness when it reads naturally, but choose explicit comparisons when the distinction '
- 'matters, such as checking whether a value is exactly None.'],
- 'code': 'print(bool(0))\nprint(bool(42))',
- 'output': 'False\nTrue',
- 'line': 48,
- 'kind': 'cell'}]},
- {'slug': 'equality-and-identity',
- 'title': 'Equality and Identity',
- 'section': 'Data Model',
- 'summary': '== compares values, while is compares object identity.',
- 'doc_url': 'https://docs.python.org/3.13/reference/expressions.html#is-not',
+ 'output': 'True\nFalse',
+ 'prose': ['Equal containers can be different objects. `==` compares list contents, '
+ 'while `is` checks whether both names refer to the same list object.']},
+ {'code': 'same = left\nsame.append(4)\nprint(left)\nprint(same is left)',
+ 'kind': 'cell',
+ 'line': 33,
+ 'output': '[1, 2, 3, 4]\nTrue',
+ 'prose': ['Identity matters when objects are mutable. `same` is another name for '
+ '`left`, so mutating through one name changes the object seen through the '
+ 'other.']},
+ {'code': 'value = None\nprint(value is None)',
+ 'kind': 'cell',
+ 'line': 49,
+ 'output': 'True',
+ 'prose': ['Use `is` for singleton identity checks such as `None`. This asks whether '
+ 'the value is the one special `None` object.']},
+ {'code': 'small_a = 100\n'
+ 'small_b = 100\n'
+ 'print(small_a is small_b)\n'
+ '\n'
+ 'big_a = int("1000")\n'
+ 'big_b = int("1000")\n'
+ 'print(big_a is big_b)\n'
+ 'print(big_a == big_b)',
+ 'kind': 'cell',
+ 'line': 62,
+ 'output': 'True\nFalse\nTrue',
+ 'prose': ['`is` for integers is unreliable because CPython caches small integers '
+ '(roughly `-5` to `256`) but not larger ones. Two equal large integers can '
+ 'be different objects. Use `==` for value comparisons; reserve `is` for '
+ 'singletons.']}],
'code': 'left = [1, 2, 3]\n'
'right = [1, 2, 3]\n'
'print(left == right)\n'
@@ -478,34 +1020,92 @@
'print(same is left)\n'
'\n'
'value = None\n'
- 'print(value is None)\n',
- 'expected_output': 'True\nFalse\n[1, 2, 3, 4]\nTrue\nTrue\n',
+ 'print(value is None)\n'
+ '\n'
+ 'small_a = 100\n'
+ 'small_b = 100\n'
+ 'print(small_a is small_b)\n'
+ '\n'
+ 'big_a = int("1000")\n'
+ 'big_b = int("1000")\n'
+ 'print(big_a is big_b)\n'
+ 'print(big_a == big_b)\n',
+ 'doc_path': '/reference/expressions.html#is-not',
+ 'doc_url': 'https://docs.python.org/3.13/reference/expressions.html#is-not',
+ 'expected_output': 'True\nFalse\n[1, 2, 3, 4]\nTrue\nTrue\nTrue\nFalse\nTrue\n',
+ 'explanation': ['Python separates equality from identity. Equality asks whether two objects '
+ 'should be considered the same value, while identity asks whether two names '
+ 'point to the same object.',
+ 'This distinction matters for mutable containers because two equal lists can '
+ 'still be independent objects. Mutating one should not imply mutating the other '
+ 'unless they share identity.',
+ 'The `is` operator is best reserved for identity checks against singletons such '
+ 'as `None`. For ordinary values, `==` is the comparison readers expect.'],
+ 'min_python': None,
'notes': ['Use `==` for ordinary value comparisons.',
'Use `is` primarily for identity checks against singletons such as `None`.',
- 'Equal mutable containers can still be independent objects.'],
- 'cells': [{'prose': ['Equal containers can be different objects. `==` compares list contents, while `is` checks '
- 'whether both names refer to the same list object.'],
- 'code': 'left = [1, 2, 3]\nright = [1, 2, 3]\nprint(left == right)\nprint(left is right)',
- 'output': 'True\nFalse',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['Identity matters when objects are mutable. `same` is another name for `left`, so mutating '
- 'through one name changes the object seen through the other.'],
- 'code': 'same = left\nsame.append(4)\nprint(left)\nprint(same is left)',
- 'output': '[1, 2, 3, 4]\nTrue',
- 'line': 33,
- 'kind': 'cell'},
- {'prose': ['Use `is` for singleton identity checks such as `None`. This asks whether the value is the one '
- 'special `None` object.'],
- 'code': 'value = None\nprint(value is None)',
- 'output': 'True',
- 'line': 49,
- 'kind': 'cell'}]},
- {'slug': 'mutability',
- 'title': 'Mutability',
+ 'Equal mutable containers can still be independent objects.',
+ "Never use `is` to compare numbers; CPython's small-integer cache makes the result an "
+ 'implementation detail.'],
'section': 'Data Model',
- 'summary': 'Some objects change in place, while others return new values.',
- 'doc_url': 'https://docs.python.org/3.13/reference/datamodel.html#objects-values-and-types',
+ 'see_also': [],
+ 'slug': 'equality-and-identity',
+ 'summary': '== compares values, while is compares object identity.',
+ 'title': 'Equality and Identity',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'left = [1, 2, 3]\n'
+ 'right = [1, 2, 3]\n'
+ 'print(left == right)\n'
+ 'print(left is right)',
+ 'prose': 'Equal containers can be different objects. `==` compares list '
+ 'contents, while `is` checks whether both names refer to the same list '
+ 'object.'},
+ {'code': 'same = left\nsame.append(4)\nprint(left)\nprint(same is left)',
+ 'prose': 'Identity matters when objects are mutable. `same` is another name for '
+ '`left`, so mutating through one name changes the object seen through '
+ 'the other.'},
+ {'code': 'value = None\nprint(value is None)',
+ 'prose': 'Use `is` for singleton identity checks such as `None`. This asks '
+ 'whether the value is the one special `None` object.'},
+ {'code': 'small_a = 100\n'
+ 'small_b = 100\n'
+ 'print(small_a is small_b)\n'
+ '\n'
+ 'big_a = int("1000")\n'
+ 'big_b = int("1000")\n'
+ 'print(big_a is big_b)\n'
+ 'print(big_a == big_b)',
+ 'prose': '`is` for integers is unreliable because CPython caches small integers '
+ '(roughly `-5` to `256`) but not larger ones. Two equal large integers '
+ 'can be different objects. Use `==` for value comparisons; reserve '
+ '`is` for singletons.'}]},
+ {'cells': [{'code': 'first = ["python"]\n'
+ 'second = first\n'
+ 'second.append("workers")\n'
+ 'print(first)\n'
+ 'print(second)',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': "['python', 'workers']\n['python', 'workers']",
+ 'prose': ['Mutable objects can change in place. `first` and `second` point to the '
+ 'same list, so appending through one name changes the object seen through '
+ 'both names.']},
+ {'code': 'text = "python"\nupper_text = text.upper()\nprint(text)\nprint(upper_text)',
+ 'kind': 'cell',
+ 'line': 34,
+ 'output': 'python\nPYTHON',
+ 'prose': ['Immutable objects do not change in place. String methods such as `upper()` '
+ 'return a new string, leaving the original string unchanged.']},
+ {'code': 'numbers = [3, 1, 2]\n'
+ 'ordered = sorted(numbers)\n'
+ 'print(ordered)\n'
+ 'print(numbers)',
+ 'kind': 'cell',
+ 'line': 50,
+ 'output': '[1, 2, 3]\n[3, 1, 2]',
+ 'prose': ['Some APIs make the boundary explicit. `sorted()` returns a new list, while '
+ 'methods such as `append()` and `list.sort()` mutate an existing list.']}],
'code': 'first = ["python"]\n'
'second = first\n'
'second.append("workers")\n'
@@ -521,33 +1121,77 @@
'ordered = sorted(numbers)\n'
'print(ordered)\n'
'print(numbers)\n',
- 'expected_output': "['python', 'workers']\n['python', 'workers']\npython\nPYTHON\n[1, 2, 3]\n[3, 1, 2]\n",
+ 'doc_path': '/reference/datamodel.html#objects-values-and-types',
+ 'doc_url': 'https://docs.python.org/3.13/reference/datamodel.html#objects-values-and-types',
+ 'expected_output': "['python', 'workers']\n"
+ "['python', 'workers']\n"
+ 'python\n'
+ 'PYTHON\n'
+ '[1, 2, 3]\n'
+ '[3, 1, 2]\n',
+ 'explanation': ['Objects in Python can be mutable or immutable. Mutable objects such as lists '
+ 'and dictionaries can change in place, while immutable objects such as strings '
+ 'and tuples produce new values instead.',
+ 'Names can share one mutable object, so a change through one name is visible '
+ 'through another. This is powerful, but it is also the source of many beginner '
+ 'surprises.',
+ 'The boundary matters across Python: `append()` mutates a list, string methods '
+ 'return new strings, and `sorted()` returns a new list while `list.sort()` '
+ 'mutates an existing one.'],
+ 'min_python': None,
'notes': ['Lists and dictionaries are mutable; strings and tuples are immutable.',
'Aliasing is useful, but copy mutable containers when independent changes are needed.',
'Pay attention to whether an operation mutates in place or returns a new value.'],
- 'cells': [{'prose': ['Mutable objects can change in place. `first` and `second` point to the same list, so appending '
- 'through one name changes the object seen through both names.'],
- 'code': 'first = ["python"]\nsecond = first\nsecond.append("workers")\nprint(first)\nprint(second)',
- 'output': "['python', 'workers']\n['python', 'workers']",
+ 'section': 'Data Model',
+ 'see_also': [],
+ 'slug': 'mutability',
+ 'summary': 'Some objects change in place, while others return new values.',
+ 'title': 'Mutability',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'first = ["python"]\n'
+ 'second = first\n'
+ 'second.append("workers")\n'
+ 'print(first)\n'
+ 'print(second)',
+ 'prose': 'Mutable objects can change in place. `first` and `second` point to '
+ 'the same list, so appending through one name changes the object seen '
+ 'through both names.'},
+ {'code': 'text = "python"\n'
+ 'upper_text = text.upper()\n'
+ 'print(text)\n'
+ 'print(upper_text)',
+ 'prose': 'Immutable objects do not change in place. String methods such as '
+ '`upper()` return a new string, leaving the original string '
+ 'unchanged.'},
+ {'code': 'numbers = [3, 1, 2]\n'
+ 'ordered = sorted(numbers)\n'
+ 'print(ordered)\n'
+ 'print(numbers)',
+ 'prose': 'Some APIs make the boundary explicit. `sorted()` returns a new list, '
+ 'while methods such as `append()` and `list.sort()` mutate an existing '
+ 'list.'}]},
+ {'cells': [{'code': 'import gc\n'
+ '\n'
+ 'names = []\n'
+ 'alias = names\n'
+ 'alias.append("Ada")\n'
+ '\n'
+ 'print(names is alias)\n'
+ 'print(names)\n'
+ '\n'
+ 'object_id = id(names)\n'
+ 'del alias\n'
+ 'print(id(names) == object_id)\n'
+ '\n'
+ 'del names\n'
+ 'print("object can be reclaimed")\n'
+ 'gc.collect()',
+ 'kind': 'cell',
'line': 17,
- 'kind': 'cell'},
- {'prose': ['Immutable objects do not change in place. String methods such as `upper()` return a new '
- 'string, leaving the original string unchanged.'],
- 'code': 'text = "python"\nupper_text = text.upper()\nprint(text)\nprint(upper_text)',
- 'output': 'python\nPYTHON',
- 'line': 34,
- 'kind': 'cell'},
- {'prose': ['Some APIs make the boundary explicit. `sorted()` returns a new list, while methods such as '
- '`append()` and `list.sort()` mutate an existing list.'],
- 'code': 'numbers = [3, 1, 2]\nordered = sorted(numbers)\nprint(ordered)\nprint(numbers)',
- 'output': '[1, 2, 3]\n[3, 1, 2]',
- 'line': 50,
- 'kind': 'cell'}]},
- {'slug': 'object-lifecycle',
- 'title': 'Object Lifecycle',
- 'section': 'Basics',
- 'summary': 'References keep objects alive until Python can reclaim them.',
- 'doc_url': 'https://docs.python.org/3.13/reference/datamodel.html#objects-values-and-types',
+ 'output': "True\n['Ada']\nTrue\nobject can be reclaimed",
+ 'prose': ['Use `is` and `id()` to observe identity while two names refer to the same '
+ 'object.']}],
'code': 'import gc\n'
'\n'
'names = []\n'
@@ -564,37 +1208,77 @@
'del names\n'
'print("object can be reclaimed")\n'
'gc.collect()\n',
+ 'doc_path': '/reference/datamodel.html#objects-values-and-types',
+ 'doc_url': 'https://docs.python.org/3.13/reference/datamodel.html#objects-values-and-types',
'expected_output': "True\n['Ada']\nTrue\nobject can be reclaimed\n",
+ 'explanation': ['References keep objects alive until Python can reclaim them. It exists to make '
+ 'a common boundary explicit instead of leaving the behavior implicit in a larger '
+ 'program.',
+ 'Use it when the problem shape matches the example, and prefer simpler '
+ 'neighboring tools when the extra machinery would hide the intent. The notes '
+ 'call out the boundary so the feature stays practical rather than decorative.',
+ 'The example is small, deterministic, and focused on the semantic point. The '
+ 'complete source is editable below, while the walkthrough pairs the source with '
+ 'its output.'],
+ 'min_python': None,
'notes': ['Use `is` and `id()` to observe identity while two names refer to the same object.',
- 'Deleting a name removes one reference; it does not directly destroy the object if another reference still '
- 'exists.',
- 'Python reclaims unreachable objects automatically, so programs usually manage ownership by controlling '
- 'references.'],
- 'cells': [{'prose': ['Use `is` and `id()` to observe identity while two names refer to the same object.'],
- 'code': 'import gc\n'
- '\n'
- 'names = []\n'
- 'alias = names\n'
- 'alias.append("Ada")\n'
- '\n'
- 'print(names is alias)\n'
- 'print(names)\n'
- '\n'
- 'object_id = id(names)\n'
- 'del alias\n'
- 'print(id(names) == object_id)\n'
+ 'Deleting a name removes one reference; it does not directly destroy the object if '
+ 'another reference still exists.',
+ 'Python reclaims unreachable objects automatically, so programs usually manage '
+ 'ownership by controlling references.'],
+ 'section': 'Basics',
+ 'see_also': [],
+ 'slug': 'object-lifecycle',
+ 'summary': 'References keep objects alive until Python can reclaim them.',
+ 'title': 'Object Lifecycle',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'import gc\n'
+ '\n'
+ 'names = []\n'
+ 'alias = names\n'
+ 'alias.append("Ada")\n'
+ '\n'
+ 'print(names is alias)\n'
+ 'print(names)\n'
+ '\n'
+ 'object_id = id(names)\n'
+ 'del alias\n'
+ 'print(id(names) == object_id)\n'
+ '\n'
+ 'del names\n'
+ 'print("object can be reclaimed")\n'
+ 'gc.collect()',
+ 'prose': 'Use `is` and `id()` to observe identity while two names refer to the '
+ 'same object.'}]},
+ {'cells': [{'code': 'english = "hello"\n'
+ 'thai = "สวัสดี"\n'
'\n'
- 'del names\n'
- 'print("object can be reclaimed")\n'
- 'gc.collect()',
- 'output': "True\n['Ada']\nTrue\nobject can be reclaimed",
+ 'for label, word in [("English", english), ("Thai", thai)]:\n'
+ ' print(label, word, len(word), len(word.encode("utf-8")))',
+ 'kind': 'cell',
'line': 17,
- 'kind': 'cell'}]},
- {'slug': 'strings',
- 'title': 'Strings',
- 'section': 'Text',
- 'summary': 'Strings are immutable Unicode text sequences.',
- 'doc_url': 'https://docs.python.org/3.13/library/stdtypes.html#text-sequence-type-str',
+ 'output': 'English hello 5 5\nThai สวัสดี 6 18',
+ 'prose': ['Compare an English greeting with a Thai greeting. Both are Python `str` '
+ 'values, but UTF-8 uses one byte for each ASCII code point and multiple '
+ 'bytes for many non-ASCII code points.']},
+ {'code': 'print(thai[0])\nprint([hex(ord(char)) for char in thai[:2]])',
+ 'kind': 'cell',
+ 'line': 34,
+ 'output': "ส\n['0xe2a', '0xe27']",
+ 'prose': ['Indexing and iteration work with Unicode code points, not encoded bytes. '
+ '`ord()` returns the integer code point, which is often displayed in '
+ 'hexadecimal when teaching text encoding.']},
+ {'code': 'text = " café "\n'
+ 'clean = text.strip()\n'
+ 'print(clean)\n'
+ 'print(clean.upper())\n'
+ 'print(clean.encode("utf-8"))',
+ 'kind': 'cell',
+ 'line': 48,
+ 'output': "café\nCAFÉ\nb'caf\\xc3\\xa9'",
+ 'prose': ['String methods return new strings because strings are immutable. Encoding '
+ 'turns text into bytes when another system needs a byte representation.']}],
'code': 'english = "hello"\n'
'thai = "สวัสดี"\n'
'\n'
@@ -609,44 +1293,87 @@
'print(clean)\n'
'print(clean.upper())\n'
'print(clean.encode("utf-8"))\n',
- 'expected_output': "English hello 5 5\nThai สวัสดี 6 18\nส\n['0xe2a', '0xe27']\ncafé\nCAFÉ\nb'caf\\xc3\\xa9'\n",
+ 'doc_path': '/library/stdtypes.html#text-sequence-type-str',
+ 'doc_url': 'https://docs.python.org/3.13/library/stdtypes.html#text-sequence-type-str',
+ 'expected_output': 'English hello 5 5\n'
+ 'Thai สวัสดี 6 18\n'
+ 'ส\n'
+ "['0xe2a', '0xe27']\n"
+ 'café\n'
+ 'CAFÉ\n'
+ "b'caf\\xc3\\xa9'\n",
+ 'explanation': ['Python strings are immutable Unicode text sequences. A `str` stores text as '
+ 'Unicode code points, so it can represent English, Thai, accented letters, '
+ 'emoji, and ordinary ASCII with the same type.',
+ 'Unicode matters because text length and byte length are different questions. '
+ 'The English word `"hello"` uses five code points and five UTF-8 bytes because '
+ 'ASCII characters encode as one byte each. The Thai greeting `"สวัสดี"` has six '
+ 'code points but needs eighteen UTF-8 bytes.',
+ 'Use `str` when you mean text, and encode to `bytes` only at boundaries such as '
+ 'files, network protocols, and binary APIs. String operations such as `upper()` '
+ 'and `strip()` return new strings instead of changing the original.'],
+ 'min_python': None,
'notes': ['Use `str` for text and `bytes` for binary data.',
- '`len(text)` counts Unicode code points; `len(text.encode("utf-8"))` counts encoded bytes.',
+ '`len(text)` counts Unicode code points; `len(text.encode("utf-8"))` counts encoded '
+ 'bytes.',
'ASCII text is a useful baseline because each ASCII code point is one UTF-8 byte.',
'String methods return new strings because strings are immutable.',
- 'User-visible “characters” can be more subtle than code points; combining marks and emoji sequences may '
- 'need specialized text handling.'],
- 'cells': [{'prose': ['Compare an English greeting with a Thai greeting. Both are Python `str` values, but UTF-8 uses '
- 'one byte for each ASCII code point and multiple bytes for many non-ASCII code points.'],
- 'code': 'english = "hello"\n'
- 'thai = "สวัสดี"\n'
- '\n'
- 'for label, word in [("English", english), ("Thai", thai)]:\n'
- ' print(label, word, len(word), len(word.encode("utf-8")))',
- 'output': 'English hello 5 5\nThai สวัสดี 6 18',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['Indexing and iteration work with Unicode code points, not encoded bytes. `ord()` returns the '
- 'integer code point, which is often displayed in hexadecimal when teaching text encoding.'],
- 'code': 'print(thai[0])\nprint([hex(ord(char)) for char in thai[:2]])',
- 'output': "ส\n['0xe2a', '0xe27']",
- 'line': 34,
- 'kind': 'cell'},
- {'prose': ['String methods return new strings because strings are immutable. Encoding turns text into '
- 'bytes when another system needs a byte representation.'],
- 'code': 'text = " café "\n'
- 'clean = text.strip()\n'
- 'print(clean)\n'
- 'print(clean.upper())\n'
- 'print(clean.encode("utf-8"))',
- 'output': "café\nCAFÉ\nb'caf\\xc3\\xa9'",
- 'line': 48,
- 'kind': 'cell'}]},
- {'slug': 'bytes-and-bytearray',
- 'title': 'Bytes and Bytearray',
- 'section': 'Basics',
- 'summary': 'bytes and bytearray store binary data, not Unicode text.',
- 'doc_url': 'https://docs.python.org/3.13/library/stdtypes.html#binary-sequence-types-bytes-bytearray-memoryview',
+ 'User-visible “characters” can be more subtle than code points; combining marks and '
+ 'emoji sequences may need specialized text handling.'],
+ 'section': 'Text',
+ 'see_also': [],
+ 'slug': 'strings',
+ 'summary': 'Strings are immutable Unicode text sequences.',
+ 'title': 'Strings',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'english = "hello"\n'
+ 'thai = "สวัสดี"\n'
+ '\n'
+ 'for label, word in [("English", english), ("Thai", thai)]:\n'
+ ' print(label, word, len(word), len(word.encode("utf-8")))',
+ 'prose': 'Compare an English greeting with a Thai greeting. Both are Python '
+ '`str` values, but UTF-8 uses one byte for each ASCII code point and '
+ 'multiple bytes for many non-ASCII code points.'},
+ {'code': 'print(thai[0])\nprint([hex(ord(char)) for char in thai[:2]])',
+ 'prose': 'Indexing and iteration work with Unicode code points, not encoded '
+ 'bytes. `ord()` returns the integer code point, which is often '
+ 'displayed in hexadecimal when teaching text encoding.'},
+ {'code': 'text = " café "\n'
+ 'clean = text.strip()\n'
+ 'print(clean)\n'
+ 'print(clean.upper())\n'
+ 'print(clean.encode("utf-8"))',
+ 'prose': 'String methods return new strings because strings are immutable. '
+ 'Encoding turns text into bytes when another system needs a byte '
+ 'representation.'}]},
+ {'cells': [{'code': 'text = "café"\n'
+ 'data = text.encode("utf-8")\n'
+ 'print(data)\n'
+ 'print(len(text), len(data))',
+ 'kind': 'cell',
+ 'line': 22,
+ 'output': "b'caf\\xc3\\xa9'\n4 5",
+ 'prose': ['Encode text when an external boundary needs bytes. UTF-8 uses one byte for '
+ 'ASCII characters and more than one byte for many other characters.']},
+ {'code': 'print(data.decode("utf-8"))',
+ 'kind': 'cell',
+ 'line': 38,
+ 'output': 'café',
+ 'prose': ['Decode bytes when the program needs text again. The decoder must match the '
+ 'encoding used at the boundary.']},
+ {'code': 'print(data[0])',
+ 'kind': 'cell',
+ 'line': 50,
+ 'output': '99',
+ 'prose': ['Indexing a `bytes` object returns an integer byte value, not a '
+ 'one-character `bytes` object.']},
+ {'code': 'packet = bytearray(b"py")\npacket[0] = ord("P")\nprint(packet)',
+ 'kind': 'cell',
+ 'line': 62,
+ 'output': "bytearray(b'Py')",
+ 'prose': ['`bytes` is immutable. Use `bytearray` when binary data must be changed in '
+ 'place.']}],
'code': 'text = "café"\n'
'data = text.encode("utf-8")\n'
'\n'
@@ -658,38 +1385,70 @@
'packet = bytearray(b"py")\n'
'packet[0] = ord("P")\n'
'print(packet)\n',
+ 'doc_path': '/library/stdtypes.html#binary-sequence-types-bytes-bytearray-memoryview',
+ 'doc_url': 'https://docs.python.org/3.13/library/stdtypes.html#binary-sequence-types-bytes-bytearray-memoryview',
'expected_output': "b'caf\\xc3\\xa9'\n4 5\ncafé\n99\nbytearray(b'Py')\n",
+ 'explanation': ['`str` stores Unicode text. `bytes` stores raw byte values. The boundary matters '
+ 'whenever text leaves Python for a file, network protocol, subprocess, or binary '
+ 'format.',
+ 'Encoding turns text into bytes with a named encoding such as UTF-8. Decoding '
+ 'turns bytes back into text. The lengths can differ because one Unicode '
+ 'character may require several bytes.',
+ 'Use immutable `bytes` for stable binary data and `bytearray` when the bytes '
+ 'must be changed in place.'],
+ 'min_python': None,
'notes': ['Encode text when an external boundary needs bytes.',
'Decode bytes when you want text again.',
'Indexing `bytes` returns integers from 0 to 255.',
'Use `bytearray` when binary data must be changed in place.'],
- 'cells': [{'prose': ['Encode text when an external boundary needs bytes. UTF-8 uses one byte for ASCII characters '
- 'and more than one byte for many other characters.'],
- 'code': 'text = "café"\ndata = text.encode("utf-8")\nprint(data)\nprint(len(text), len(data))',
- 'output': "b'caf\\xc3\\xa9'\n4 5",
- 'line': 22,
- 'kind': 'cell'},
- {'prose': ['Decode bytes when the program needs text again. The decoder must match the encoding used at '
- 'the boundary.'],
- 'code': 'print(data.decode("utf-8"))',
- 'output': 'café',
- 'line': 38,
- 'kind': 'cell'},
- {'prose': ['Indexing a `bytes` object returns an integer byte value, not a one-character `bytes` object.'],
- 'code': 'print(data[0])',
- 'output': '99',
- 'line': 50,
- 'kind': 'cell'},
- {'prose': ['`bytes` is immutable. Use `bytearray` when binary data must be changed in place.'],
- 'code': 'packet = bytearray(b"py")\npacket[0] = ord("P")\nprint(packet)',
- 'output': "bytearray(b'Py')",
- 'line': 62,
- 'kind': 'cell'}]},
- {'slug': 'string-formatting',
- 'title': 'String Formatting',
- 'section': 'Text',
- 'summary': 'f-strings turn values into readable text at the point of use.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/inputoutput.html#formatted-string-literals',
+ 'section': 'Basics',
+ 'see_also': ['strings', 'literals', 'networking'],
+ 'slug': 'bytes-and-bytearray',
+ 'summary': 'bytes and bytearray store binary data, not Unicode text.',
+ 'title': 'Bytes and Bytearray',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'text = "café"\n'
+ 'data = text.encode("utf-8")\n'
+ 'print(data)\n'
+ 'print(len(text), len(data))',
+ 'prose': 'Encode text when an external boundary needs bytes. UTF-8 uses one '
+ 'byte for ASCII characters and more than one byte for many other '
+ 'characters.'},
+ {'code': 'print(data.decode("utf-8"))',
+ 'prose': 'Decode bytes when the program needs text again. The decoder must '
+ 'match the encoding used at the boundary.'},
+ {'code': 'print(data[0])',
+ 'prose': 'Indexing a `bytes` object returns an integer byte value, not a '
+ 'one-character `bytes` object.'},
+ {'code': 'packet = bytearray(b"py")\npacket[0] = ord("P")\nprint(packet)',
+ 'prose': '`bytes` is immutable. Use `bytearray` when binary data must be '
+ 'changed in place.'}]},
+ {'cells': [{'code': 'name = "Ada"\n'
+ 'score = 9.5\n'
+ 'rank = 1\n'
+ '\n'
+ 'message = f"{name} scored {score}"\n'
+ 'print(message)',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': 'Ada scored 9.5',
+ 'prose': ['An f-string evaluates expressions inside braces and inserts their string '
+ 'form into the surrounding text. This is clearer than joining several '
+ 'converted values by hand.']},
+ {'code': 'row = f"{rank:>2} | {name:<8} | {score:05.1f}"\nprint(row)',
+ 'kind': 'cell',
+ 'line': 34,
+ 'output': ' 1 | Ada | 009.5',
+ 'prose': ['Format specifications after `:` control display without changing the '
+ 'underlying values. Here the rank is right-aligned, the name is '
+ 'left-aligned, and the score is padded to one decimal place.']},
+ {'code': 'print(f"{score = }")',
+ 'kind': 'cell',
+ 'line': 47,
+ 'output': 'score = 9.5',
+ 'prose': ['The debug form with `=` is useful while learning or logging because it '
+ 'prints both the expression and the value.']}],
'code': 'name = "Ada"\n'
'score = 9.5\n'
'rank = 1\n'
@@ -701,34 +1460,72 @@
'print(row)\n'
'\n'
'print(f"{score = }")\n',
+ 'doc_path': '/tutorial/inputoutput.html#formatted-string-literals',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/inputoutput.html#formatted-string-literals',
'expected_output': 'Ada scored 9.5\n 1 | Ada | 009.5\nscore = 9.5\n',
+ 'explanation': ['Formatted string literals, or f-strings, exist because programs constantly need '
+ 'to turn values into human-readable text. They keep the expression next to the '
+ 'words it explains.',
+ 'Format specifications after `:` control presentation details such as width, '
+ 'alignment, padding, and precision. This separates the value being computed from '
+ 'the way it should be displayed.',
+ 'Use f-strings for most new formatting code. They relate directly to '
+ 'expressions: anything inside braces is evaluated, then formatted into the '
+ 'surrounding string.'],
+ 'min_python': None,
'notes': ['Use `f"..."` strings for most new formatting code.',
'Expressions inside braces are evaluated before formatting.',
'Format specifications after `:` control alignment, width, padding, and precision.'],
- 'cells': [{'prose': ['An f-string evaluates expressions inside braces and inserts their string form into the '
- 'surrounding text. This is clearer than joining several converted values by hand.'],
- 'code': 'name = "Ada"\nscore = 9.5\nrank = 1\n\nmessage = f"{name} scored {score}"\nprint(message)',
- 'output': 'Ada scored 9.5',
+ 'section': 'Text',
+ 'see_also': [],
+ 'slug': 'string-formatting',
+ 'summary': 'f-strings turn values into readable text at the point of use.',
+ 'title': 'String Formatting',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'name = "Ada"\n'
+ 'score = 9.5\n'
+ 'rank = 1\n'
+ '\n'
+ 'message = f"{name} scored {score}"\n'
+ 'print(message)',
+ 'prose': 'An f-string evaluates expressions inside braces and inserts their '
+ 'string form into the surrounding text. This is clearer than joining '
+ 'several converted values by hand.'},
+ {'code': 'row = f"{rank:>2} | {name:<8} | {score:05.1f}"\nprint(row)',
+ 'prose': 'Format specifications after `:` control display without changing the '
+ 'underlying values. Here the rank is right-aligned, the name is '
+ 'left-aligned, and the score is padded to one decimal place.'},
+ {'code': 'print(f"{score = }")',
+ 'prose': 'The debug form with `=` is useful while learning or logging because '
+ 'it prints both the expression and the value.'}]},
+ {'cells': [{'code': 'temperature = 72\n'
+ '\n'
+ 'if temperature < 60:\n'
+ ' print("cold")\n'
+ 'elif temperature < 80:\n'
+ ' print("comfortable")\n'
+ 'else:\n'
+ ' print("hot")',
+ 'kind': 'cell',
'line': 17,
- 'kind': 'cell'},
- {'prose': ['Format specifications after `:` control display without changing the underlying values. Here '
- 'the rank is right-aligned, the name is left-aligned, and the score is padded to one decimal '
- 'place.'],
- 'code': 'row = f"{rank:>2} | {name:<8} | {score:05.1f}"\nprint(row)',
- 'output': ' 1 | Ada | 009.5',
- 'line': 34,
- 'kind': 'cell'},
- {'prose': ['The debug form with `=` is useful while learning or logging because it prints both the '
- 'expression and the value.'],
- 'code': 'print(f"{score = }")',
- 'output': 'score = 9.5',
- 'line': 47,
- 'kind': 'cell'}]},
- {'slug': 'conditionals',
- 'title': 'Conditionals',
- 'section': 'Control Flow',
- 'summary': 'if, elif, and else choose which block runs.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#if-statements',
+ 'output': 'comfortable',
+ 'prose': ['Start with the value that the branches will test. A conditional is only '
+ 'useful when the branch condition is visible and meaningful.',
+ 'Use `if`, `elif`, and `else` for one ordered choice. Python tests the '
+ 'branches from top to bottom and runs only the first matching block.']},
+ {'code': 'items = ["coat", "hat"]\nif items:\n print(f"packing {len(items)} items")',
+ 'kind': 'cell',
+ 'line': 38,
+ 'output': 'packing 2 items',
+ 'prose': ['Truthiness is part of conditional flow. Empty collections are false, so '
+ '`if items:` reads as “if there is anything to work with.”']},
+ {'code': 'status = "ok" if temperature < 90 else "danger"\nprint(status)',
+ 'kind': 'cell',
+ 'line': 52,
+ 'output': 'ok',
+ 'prose': ['Use the ternary expression when you are choosing a value. If either side '
+ 'needs multiple statements, use a normal `if` block instead.']}],
'code': 'temperature = 72\n'
'\n'
'if temperature < 60:\n'
@@ -744,42 +1541,73 @@
'\n'
'status = "ok" if temperature < 90 else "danger"\n'
'print(status)\n',
+ 'doc_path': '/tutorial/controlflow.html#if-statements',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#if-statements',
'expected_output': 'comfortable\npacking 2 items\nok\n',
- 'notes': ['Python has no mandatory parentheses around conditions; the colon and indentation define the block.',
+ 'explanation': ['`if`, `elif`, and `else` let a program choose one path based on a condition. '
+ 'Python uses indentation to show which statements belong to each branch.',
+ 'Conditions use Python truthiness: booleans work directly, and many objects such '
+ 'as empty lists or empty strings are considered false. Order branches from most '
+ 'specific to most general.',
+ "Use `elif` to keep one decision flat instead of nested. Use Python's ternary "
+ 'expression only when you are choosing between two values.'],
+ 'min_python': None,
+ 'notes': ['Python has no mandatory parentheses around conditions; the colon and indentation '
+ 'define the block.',
'Comparison operators such as `<` and `==` can be chained, as in `0 < value < 10`.',
- 'Keep branch bodies short; move larger work into functions so the decision remains easy to scan.'],
- 'cells': [{'prose': ['Start with the value that the branches will test. A conditional is only useful when the branch '
- 'condition is visible and meaningful.',
- 'Use `if`, `elif`, and `else` for one ordered choice. Python tests the branches from top to '
- 'bottom and runs only the first matching block.'],
- 'code': 'temperature = 72\n'
+ 'Keep branch bodies short; move larger work into functions so the decision remains '
+ 'easy to scan.'],
+ 'section': 'Control Flow',
+ 'see_also': [],
+ 'slug': 'conditionals',
+ 'summary': 'if, elif, and else choose which block runs.',
+ 'title': 'Conditionals',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'temperature = 72\n'
+ '\n'
+ 'if temperature < 60:\n'
+ ' print("cold")\n'
+ 'elif temperature < 80:\n'
+ ' print("comfortable")\n'
+ 'else:\n'
+ ' print("hot")',
+ 'prose': 'Start with the value that the branches will test. A conditional is '
+ 'only useful when the branch condition is visible and meaningful.'},
+ {'code': 'temperature = 72\n'
+ '\n'
+ 'if temperature < 60:\n'
+ ' print("cold")\n'
+ 'elif temperature < 80:\n'
+ ' print("comfortable")\n'
+ 'else:\n'
+ ' print("hot")',
+ 'prose': 'Use `if`, `elif`, and `else` for one ordered choice. Python tests the '
+ 'branches from top to bottom and runs only the first matching block.'},
+ {'code': 'items = ["coat", "hat"]\n'
+ 'if items:\n'
+ ' print(f"packing {len(items)} items")',
+ 'prose': 'Truthiness is part of conditional flow. Empty collections are false, '
+ 'so `if items:` reads as “if there is anything to work with.”'},
+ {'code': 'status = "ok" if temperature < 90 else "danger"\nprint(status)',
+ 'prose': 'Use the ternary expression when you are choosing a value. If either '
+ 'side needs multiple statements, use a normal `if` block instead.'}]},
+ {'cells': [{'code': 'def price_after_discount(price, percent):\n'
+ ' if price < 0:\n'
+ ' return "invalid price"\n'
+ ' if not 0 <= percent <= 100:\n'
+ ' return "invalid discount"\n'
'\n'
- 'if temperature < 60:\n'
- ' print("cold")\n'
- 'elif temperature < 80:\n'
- ' print("comfortable")\n'
- 'else:\n'
- ' print("hot")',
- 'output': 'comfortable',
+ ' discount = price * percent / 100\n'
+ ' return round(price - discount, 2)\n'
+ '\n'
+ 'print(price_after_discount(100, 15))\n'
+ 'print(price_after_discount(-5, 10))\n'
+ 'print(price_after_discount(100, 120))',
+ 'kind': 'cell',
'line': 17,
- 'kind': 'cell'},
- {'prose': ['Truthiness is part of conditional flow. Empty collections are false, so `if items:` reads as '
- '“if there is anything to work with.”'],
- 'code': 'items = ["coat", "hat"]\nif items:\n print(f"packing {len(items)} items")',
- 'output': 'packing 2 items',
- 'line': 38,
- 'kind': 'cell'},
- {'prose': ['Use the ternary expression when you are choosing a value. If either side needs multiple '
- 'statements, use a normal `if` block instead.'],
- 'code': 'status = "ok" if temperature < 90 else "danger"\nprint(status)',
- 'output': 'ok',
- 'line': 52,
- 'kind': 'cell'}]},
- {'slug': 'guard-clauses',
- 'title': 'Guard Clauses',
- 'section': 'Control Flow',
- 'summary': 'Guard clauses handle exceptional cases early so the main path stays flat.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#if-statements',
+ 'output': '85.0\ninvalid price\ninvalid discount',
+ 'prose': ['Return early when inputs cannot be handled.']}],
'code': 'def price_after_discount(price, percent):\n'
' if price < 0:\n'
' return "invalid price"\n'
@@ -792,31 +1620,61 @@
'print(price_after_discount(100, 15))\n'
'print(price_after_discount(-5, 10))\n'
'print(price_after_discount(100, 120))\n',
+ 'doc_path': '/tutorial/controlflow.html#if-statements',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#if-statements',
'expected_output': '85.0\ninvalid price\ninvalid discount\n',
+ 'explanation': ['Guard clauses handle exceptional cases early so the main path stays flat. It '
+ 'exists to make a common boundary explicit instead of leaving the behavior '
+ 'implicit in a larger program.',
+ 'Use it when the problem shape matches the example, and prefer simpler '
+ 'neighboring tools when the extra machinery would hide the intent. The notes '
+ 'call out the boundary so the feature stays practical rather than decorative.',
+ 'The example is small, deterministic, and focused on the semantic point. The '
+ 'complete source is editable below, while the walkthrough pairs the source with '
+ 'its output.'],
+ 'min_python': None,
'notes': ['Return early when inputs cannot be handled.',
'After the guards, the remaining code can read as the normal path.',
'Guard clauses are a style choice, not new syntax.'],
- 'cells': [{'prose': ['Return early when inputs cannot be handled.'],
- 'code': 'def price_after_discount(price, percent):\n'
- ' if price < 0:\n'
- ' return "invalid price"\n'
- ' if not 0 <= percent <= 100:\n'
- ' return "invalid discount"\n'
- '\n'
- ' discount = price * percent / 100\n'
- ' return round(price - discount, 2)\n'
- '\n'
- 'print(price_after_discount(100, 15))\n'
- 'print(price_after_discount(-5, 10))\n'
- 'print(price_after_discount(100, 120))',
- 'output': '85.0\ninvalid price\ninvalid discount',
- 'line': 17,
- 'kind': 'cell'}]},
- {'slug': 'assignment-expressions',
- 'title': 'Assignment Expressions',
'section': 'Control Flow',
- 'summary': 'The walrus operator assigns a value inside an expression.',
- 'doc_url': 'https://docs.python.org/3.13/reference/expressions.html#assignment-expressions',
+ 'see_also': [],
+ 'slug': 'guard-clauses',
+ 'summary': 'Guard clauses handle exceptional cases early so the main path stays flat.',
+ 'title': 'Guard Clauses',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'def price_after_discount(price, percent):\n'
+ ' if price < 0:\n'
+ ' return "invalid price"\n'
+ ' if not 0 <= percent <= 100:\n'
+ ' return "invalid discount"\n'
+ '\n'
+ ' discount = price * percent / 100\n'
+ ' return round(price - discount, 2)\n'
+ '\n'
+ 'print(price_after_discount(100, 15))\n'
+ 'print(price_after_discount(-5, 10))\n'
+ 'print(price_after_discount(100, 120))',
+ 'prose': 'Return early when inputs cannot be handled.'}]},
+ {'cells': [{'code': 'messages = ["hello", "", "python"]\n'
+ '\n'
+ 'for message in messages:\n'
+ ' if length := len(message):\n'
+ ' print(message, length)',
+ 'kind': 'cell',
+ 'line': 22,
+ 'output': 'hello 5\npython 6',
+ 'prose': ['An assignment expression can name a computed value while a condition tests '
+ 'it. Here empty strings are skipped because their length is zero.']},
+ {'code': 'queue = ["retry", "ok"]\n'
+ 'while (status := queue.pop(0)) != "ok":\n'
+ ' print(status)\n'
+ 'print(status)',
+ 'kind': 'cell',
+ 'line': 39,
+ 'output': 'retry\nok',
+ 'prose': ['The same idea works in loops that read state until a sentinel appears. The '
+ 'assignment and comparison stay together.']}],
'code': 'messages = ["hello", "", "python"]\n'
'\n'
'for message in messages:\n'
@@ -827,54 +1685,104 @@
'while (status := queue.pop(0)) != "ok":\n'
' print(status)\n'
'print(status)\n',
+ 'doc_path': '/reference/expressions.html#assignment-expressions',
+ 'doc_url': 'https://docs.python.org/3.13/reference/expressions.html#assignment-expressions',
'expected_output': 'hello 5\npython 6\nretry\nok\n',
+ 'explanation': ['The assignment expression operator `:=` assigns a name while evaluating an '
+ 'expression. It is often called the walrus operator.',
+ 'Use it when computing a value and testing it are naturally one step. Avoid it '
+ 'when a separate assignment would make the code easier to read.',
+ 'The boundary is readability: the walrus operator can remove duplication, but it '
+ 'should not hide important state changes.'],
+ 'min_python': None,
'notes': ['`name := expression` assigns and evaluates to the assigned value.',
'Use it to avoid computing the same value twice.',
'Prefer a normal assignment when the expression becomes hard to scan.'],
- 'cells': [{'prose': ['An assignment expression can name a computed value while a condition tests it. Here empty '
- 'strings are skipped because their length is zero.'],
- 'code': 'messages = ["hello", "", "python"]\n'
- '\n'
- 'for message in messages:\n'
- ' if length := len(message):\n'
- ' print(message, length)',
- 'output': 'hello 5\npython 6',
- 'line': 22,
- 'kind': 'cell'},
- {'prose': ['The same idea works in loops that read state until a sentinel appears. The assignment and '
- 'comparison stay together.'],
- 'code': 'queue = ["retry", "ok"]\n'
- 'while (status := queue.pop(0)) != "ok":\n'
- ' print(status)\n'
- 'print(status)',
- 'output': 'retry\nok',
- 'line': 39,
- 'kind': 'cell'}]},
- {'slug': 'for-loops',
- 'title': 'For Loops',
'section': 'Control Flow',
- 'summary': 'for iterates over any iterable object.',
+ 'see_also': ['conditionals', 'while-loops', 'variables'],
+ 'slug': 'assignment-expressions',
+ 'summary': 'The walrus operator assigns a value inside an expression.',
+ 'title': 'Assignment Expressions',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'messages = ["hello", "", "python"]\n'
+ '\n'
+ 'for message in messages:\n'
+ ' if length := len(message):\n'
+ ' print(message, length)',
+ 'prose': 'An assignment expression can name a computed value while a condition '
+ 'tests it. Here empty strings are skipped because their length is '
+ 'zero.'},
+ {'code': 'queue = ["retry", "ok"]\n'
+ 'while (status := queue.pop(0)) != "ok":\n'
+ ' print(status)\n'
+ 'print(status)',
+ 'prose': 'The same idea works in loops that read state until a sentinel '
+ 'appears. The assignment and comparison stay together.'}]},
+ {'cells': [{'code': 'for name in ["Ada", "Grace", "Guido"]:\n print(name)',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': 'Ada\nGrace\nGuido',
+ 'prose': ['Python for loops iterate over values from an iterable. This is different '
+ 'from languages where for primarily means incrementing a numeric counter.']},
+ {'code': 'for number in range(3):\n print(number)',
+ 'kind': 'cell',
+ 'line': 32,
+ 'output': '0\n1\n2',
+ 'prose': ['range() is itself an iterable that produces numbers lazily. Use it when '
+ 'you need a sequence of integers, but prefer direct iteration when you '
+ 'already have a collection.']}],
+ 'code': 'for name in ["Ada", "Grace", "Guido"]:\n'
+ ' print(name)\n'
+ '\n'
+ 'for number in range(3):\n'
+ ' print(number)\n',
+ 'doc_path': '/tutorial/controlflow.html#for-statements',
'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#for-statements',
- 'code': 'for name in ["Ada", "Grace", "Guido"]:\n print(name)\n\nfor number in range(3):\n print(number)\n',
'expected_output': 'Ada\nGrace\nGuido\n0\n1\n2\n',
+ 'explanation': ['Python for loops iterate over values from an iterable. This is different from '
+ 'languages where for primarily means incrementing a numeric counter.',
+ 'range() is itself an iterable that produces numbers lazily. Use it when you '
+ 'need a sequence of integers, but prefer direct iteration when you already have '
+ 'a collection.',
+ 'Blocks are defined by indentation. range(3) yields 0, 1, and 2.'],
+ 'min_python': None,
'notes': ['Blocks are defined by indentation.', 'range(3) yields 0, 1, and 2.'],
- 'cells': [{'prose': ['Python for loops iterate over values from an iterable. This is different from languages where '
- 'for primarily means incrementing a numeric counter.'],
- 'code': 'for name in ["Ada", "Grace", "Guido"]:\n print(name)',
- 'output': 'Ada\nGrace\nGuido',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['range() is itself an iterable that produces numbers lazily. Use it when you need a sequence of '
- 'integers, but prefer direct iteration when you already have a collection.'],
- 'code': 'for number in range(3):\n print(number)',
- 'output': '0\n1\n2',
- 'line': 32,
- 'kind': 'cell'}]},
- {'slug': 'break-and-continue',
- 'title': 'Break and Continue',
'section': 'Control Flow',
- 'summary': 'break exits a loop early, while continue skips to the next iteration.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#break-and-continue-statements',
+ 'see_also': [],
+ 'slug': 'for-loops',
+ 'summary': 'for iterates over any iterable object.',
+ 'title': 'For Loops',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'for name in ["Ada", "Grace", "Guido"]:\n print(name)',
+ 'prose': 'Python for loops iterate over values from an iterable. This is '
+ 'different from languages where for primarily means incrementing a '
+ 'numeric counter.'},
+ {'code': 'for number in range(3):\n print(number)',
+ 'prose': 'range() is itself an iterable that produces numbers lazily. Use it '
+ 'when you need a sequence of integers, but prefer direct iteration '
+ 'when you already have a collection.'}]},
+ {'cells': [{'code': 'names = ["Ada", "", "Grace"]\n'
+ 'for name in names:\n'
+ ' if not name:\n'
+ ' continue\n'
+ ' print(name)',
+ 'kind': 'cell',
+ 'line': 22,
+ 'output': 'Ada\nGrace',
+ 'prose': ['`continue` skips the rest of the current iteration. The empty name is '
+ 'ignored, and the loop moves on to the next value.']},
+ {'code': 'commands = ["load", "save", "stop", "delete"]\n'
+ 'for command in commands:\n'
+ ' if command == "stop":\n'
+ ' break\n'
+ ' print(command)',
+ 'kind': 'cell',
+ 'line': 39,
+ 'output': 'load\nsave',
+ 'prose': ['`break` exits the loop immediately. The value after `stop` is never '
+ 'processed because the loop has already ended.']}],
'code': 'names = ["Ada", "", "Grace"]\n'
'for name in names:\n'
' if not name:\n'
@@ -886,35 +1794,65 @@
' if command == "stop":\n'
' break\n'
' print(command)\n',
+ 'doc_path': '/tutorial/controlflow.html#break-and-continue-statements',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#break-and-continue-statements',
'expected_output': 'Ada\nGrace\nload\nsave\n',
+ 'explanation': ['`break` and `continue` control the nearest enclosing loop. They exist for loops '
+ 'whose body discovers an early stop rule or an item-level skip rule.',
+ 'Use `continue` when the current item should not run the rest of the body. Use '
+ '`break` when no later item should be processed.',
+ 'The alternative is ordinary `if`/`else` nesting. Prefer `break` and `continue` '
+ 'when they keep the normal path flatter and easier to read.'],
+ 'min_python': None,
'notes': ['`continue` skips to the next loop iteration.',
'`break` exits the nearest enclosing loop immediately.',
- 'Prefer plain `if`/`else` when the loop does not need early skip or early stop behavior.'],
- 'cells': [{'prose': ['`continue` skips the rest of the current iteration. The empty name is ignored, and the loop '
- 'moves on to the next value.'],
- 'code': 'names = ["Ada", "", "Grace"]\n'
+ 'Prefer plain `if`/`else` when the loop does not need early skip or early stop '
+ 'behavior.'],
+ 'section': 'Control Flow',
+ 'see_also': ['for-loops', 'while-loops', 'loop-else'],
+ 'slug': 'break-and-continue',
+ 'summary': 'break exits a loop early, while continue skips to the next iteration.',
+ 'title': 'Break and Continue',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'names = ["Ada", "", "Grace"]\n'
+ 'for name in names:\n'
+ ' if not name:\n'
+ ' continue\n'
+ ' print(name)',
+ 'prose': '`continue` skips the rest of the current iteration. The empty name is '
+ 'ignored, and the loop moves on to the next value.'},
+ {'code': 'commands = ["load", "save", "stop", "delete"]\n'
+ 'for command in commands:\n'
+ ' if command == "stop":\n'
+ ' break\n'
+ ' print(command)',
+ 'prose': '`break` exits the loop immediately. The value after `stop` is never '
+ 'processed because the loop has already ended.'}]},
+ {'cells': [{'code': 'names = ["Ada", "Grace", "Guido"]\n'
+ '\n'
'for name in names:\n'
- ' if not name:\n'
- ' continue\n'
- ' print(name)',
- 'output': 'Ada\nGrace',
+ ' if name == "Grace":\n'
+ ' print("found")\n'
+ ' break\n'
+ 'else:\n'
+ ' print("missing")',
+ 'kind': 'cell',
'line': 22,
- 'kind': 'cell'},
- {'prose': ['`break` exits the loop immediately. The value after `stop` is never processed because the loop '
- 'has already ended.'],
- 'code': 'commands = ["load", "save", "stop", "delete"]\n'
- 'for command in commands:\n'
- ' if command == "stop":\n'
+ 'output': 'found',
+ 'prose': ['If the loop reaches `break`, the `else` block is skipped. This branch '
+ 'means the search succeeded early.']},
+ {'code': 'for name in names:\n'
+ ' if name == "Linus":\n'
+ ' print("found")\n'
' break\n'
- ' print(command)',
- 'output': 'load\nsave',
- 'line': 39,
- 'kind': 'cell'}]},
- {'slug': 'loop-else',
- 'title': 'Loop Else',
- 'section': 'Control Flow',
- 'summary': 'A loop else block runs only when the loop did not end with break.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#else-clauses-on-loops',
+ 'else:\n'
+ ' print("missing")',
+ 'kind': 'cell',
+ 'line': 41,
+ 'output': 'missing',
+ 'prose': ['If the loop finishes without `break`, the `else` block runs. This branch '
+ 'means the search examined every value and found nothing.']}],
'code': 'names = ["Ada", "Grace", "Guido"]\n'
'\n'
'for name in names:\n'
@@ -930,39 +1868,66 @@
' break\n'
'else:\n'
' print("missing")\n',
+ 'doc_path': '/tutorial/controlflow.html#else-clauses-on-loops',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#else-clauses-on-loops',
'expected_output': 'found\nmissing\n',
+ 'explanation': ['Python loops can have an `else` clause. The name is surprising at first: loop '
+ '`else` means “no `break` happened,” not “the loop condition was false.”',
+ 'This is useful for searches. Put the successful early exit in `break`, then put '
+ 'the not-found path in `else`.',
+ 'Use loop `else` sparingly. It is clearest when the loop is visibly searching '
+ 'for something.'],
+ 'min_python': None,
'notes': ['Loop `else` runs when the loop was not ended by `break`.',
'It is best for search loops with a clear found/not-found split.',
'It works with both `for` and `while` loops.'],
- 'cells': [{'prose': ['If the loop reaches `break`, the `else` block is skipped. This branch means the search '
- 'succeeded early.'],
- 'code': 'names = ["Ada", "Grace", "Guido"]\n'
- '\n'
- 'for name in names:\n'
- ' if name == "Grace":\n'
- ' print("found")\n'
- ' break\n'
- 'else:\n'
- ' print("missing")',
- 'output': 'found',
+ 'section': 'Control Flow',
+ 'see_also': ['break-and-continue', 'for-loops', 'while-loops'],
+ 'slug': 'loop-else',
+ 'summary': 'A loop else block runs only when the loop did not end with break.',
+ 'title': 'Loop Else',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'names = ["Ada", "Grace", "Guido"]\n'
+ '\n'
+ 'for name in names:\n'
+ ' if name == "Grace":\n'
+ ' print("found")\n'
+ ' break\n'
+ 'else:\n'
+ ' print("missing")',
+ 'prose': 'If the loop reaches `break`, the `else` block is skipped. This branch '
+ 'means the search succeeded early.'},
+ {'code': 'for name in names:\n'
+ ' if name == "Linus":\n'
+ ' print("found")\n'
+ ' break\n'
+ 'else:\n'
+ ' print("missing")',
+ 'prose': 'If the loop finishes without `break`, the `else` block runs. This '
+ 'branch means the search examined every value and found nothing.'}]},
+ {'cells': [{'code': 'names = ["Ada", "Grace", "Guido"]\n\nfor name in names:\n print(name)',
+ 'kind': 'cell',
'line': 22,
- 'kind': 'cell'},
- {'prose': ['If the loop finishes without `break`, the `else` block runs. This branch means the search '
- 'examined every value and found nothing.'],
- 'code': 'for name in names:\n'
- ' if name == "Linus":\n'
- ' print("found")\n'
- ' break\n'
- 'else:\n'
- ' print("missing")',
- 'output': 'missing',
+ 'output': 'Ada\nGrace\nGuido',
+ 'prose': ['Start with an ordinary list. A list stores values, and a `for` loop asks '
+ 'it for one value at a time.',
+ 'When you only need the values, iterate over the collection directly. There '
+ 'is no index variable because the loop body does not need one.']},
+ {'code': 'for index, name in enumerate(names):\n print(index, name)',
+ 'kind': 'cell',
'line': 41,
- 'kind': 'cell'}]},
- {'slug': 'iterating-over-iterables',
- 'title': 'Iterating over Iterables',
- 'section': 'Iteration',
- 'summary': 'for loops consume values from any iterable object.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#for-statements',
+ 'output': '0 Ada\n1 Grace\n2 Guido',
+ 'prose': ['When you need both a position and a value, use `enumerate()`. It produces '
+ 'index/value pairs without manual indexing.']},
+ {'code': 'scores = {"Ada": 10, "Grace": 9}\n'
+ 'for name, score in scores.items():\n'
+ ' print(name, score)',
+ 'kind': 'cell',
+ 'line': 56,
+ 'output': 'Ada 10\nGrace 9',
+ 'prose': ['Dictionaries are iterable too, but `dict.items()` is the clearest way to '
+ 'say that the loop needs keys and values together.']}],
'code': 'names = ["Ada", "Grace", "Guido"]\n'
'\n'
'for name in names:\n'
@@ -974,35 +1939,70 @@
'scores = {"Ada": 10, "Grace": 9}\n'
'for name, score in scores.items():\n'
' print(name, score)\n',
+ 'doc_path': '/tutorial/controlflow.html#for-statements',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#for-statements',
'expected_output': 'Ada\nGrace\nGuido\n0 Ada\n1 Grace\n2 Guido\nAda 10\nGrace 9\n',
+ 'explanation': ["Python's `for` statement consumes values from any iterable object: lists, "
+ 'strings, dictionaries, ranges, generators, files, and many standard-library '
+ 'helpers.',
+ 'This makes iteration a value-stream protocol rather than a special case for '
+ 'arrays. The producer decides how values are made, and the loop consumes them '
+ 'one at a time.',
+ 'Use `enumerate()` when you need positions and values together, and '
+ '`dict.items()` when you need keys and values. These helpers express intent '
+ 'better than manual indexing.'],
+ 'min_python': None,
'notes': ['A `for` loop consumes values from an iterable.',
'Different producers can feed the same loop protocol.',
'Prefer `enumerate()` over `range(len(...))` when you need an index.'],
- 'cells': [{'prose': ['Start with an ordinary list. A list stores values, and a `for` loop asks it for one value at a '
- 'time.',
- 'When you only need the values, iterate over the collection directly. There is no index '
- 'variable because the loop body does not need one.'],
- 'code': 'names = ["Ada", "Grace", "Guido"]\n\nfor name in names:\n print(name)',
- 'output': 'Ada\nGrace\nGuido',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['When you need both a position and a value, use `enumerate()`. It produces index/value pairs '
- 'without manual indexing.'],
- 'code': 'for index, name in enumerate(names):\n print(index, name)',
- 'output': '0 Ada\n1 Grace\n2 Guido',
- 'line': 36,
- 'kind': 'cell'},
- {'prose': ['Dictionaries are iterable too, but `dict.items()` is the clearest way to say that the loop '
- 'needs keys and values together.'],
- 'code': 'scores = {"Ada": 10, "Grace": 9}\nfor name, score in scores.items():\n print(name, score)',
- 'output': 'Ada 10\nGrace 9',
- 'line': 51,
- 'kind': 'cell'}]},
- {'slug': 'iterators',
- 'title': 'Iterators',
'section': 'Iteration',
- 'summary': 'iter and next expose the protocol behind for loops.',
- 'doc_url': 'https://docs.python.org/3.13/library/stdtypes.html#iterator-types',
+ 'see_also': ['iterators', 'iterator-vs-iterable', 'for-loops'],
+ 'slug': 'iterating-over-iterables',
+ 'summary': 'for loops consume values from any iterable object.',
+ 'title': 'Iterating over Iterables',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'names = ["Ada", "Grace", "Guido"]\n'
+ '\n'
+ 'for name in names:\n'
+ ' print(name)',
+ 'prose': 'Start with an ordinary list. A list stores values, and a `for` loop '
+ 'asks it for one value at a time.'},
+ {'code': 'names = ["Ada", "Grace", "Guido"]\n'
+ '\n'
+ 'for name in names:\n'
+ ' print(name)',
+ 'prose': 'When you only need the values, iterate over the collection directly. '
+ 'There is no index variable because the loop body does not need one.'},
+ {'code': 'for index, name in enumerate(names):\n print(index, name)',
+ 'prose': 'When you need both a position and a value, use `enumerate()`. It '
+ 'produces index/value pairs without manual indexing.'},
+ {'code': 'scores = {"Ada": 10, "Grace": 9}\n'
+ 'for name, score in scores.items():\n'
+ ' print(name, score)',
+ 'prose': 'Dictionaries are iterable too, but `dict.items()` is the clearest way '
+ 'to say that the loop needs keys and values together.'}]},
+ {'cells': [{'code': 'names = ["Ada", "Grace", "Guido"]\n'
+ 'iterator = iter(names)\n'
+ 'print(next(iterator))\n'
+ 'print(next(iterator))',
+ 'kind': 'cell',
+ 'line': 22,
+ 'output': 'Ada\nGrace',
+ 'prose': ['`iter()` asks an iterable for an iterator. `next()` consumes one value and '
+ "advances the iterator's position."]},
+ {'code': 'for name in iterator:\n print(name)',
+ 'kind': 'cell',
+ 'line': 38,
+ 'output': 'Guido',
+ 'prose': ['A `for` loop consumes the same iterator protocol. Because two values were '
+ 'already consumed, the loop sees only the remaining value.']},
+ {'code': 'again = iter(names)\nprint(next(again))',
+ 'kind': 'cell',
+ 'line': 51,
+ 'output': 'Ada',
+ 'prose': ['The list itself is reusable. Asking it for a fresh iterator starts a new '
+ 'pass over the same stored values.']}],
'code': 'names = ["Ada", "Grace", "Guido"]\n'
'iterator = iter(names)\n'
'print(next(iterator))\n'
@@ -1013,36 +2013,196 @@
'\n'
'again = iter(names)\n'
'print(next(again))\n',
+ 'doc_path': '/library/stdtypes.html#iterator-types',
+ 'doc_url': 'https://docs.python.org/3.13/library/stdtypes.html#iterator-types',
'expected_output': 'Ada\nGrace\nGuido\nAda\n',
+ 'explanation': ['An iterable is an object that can produce values for a loop. An iterator is the '
+ 'object that remembers where that production currently is.',
+ '`iter()` asks an iterable for an iterator, and `next()` consumes one value from '
+ 'that iterator. A `for` loop performs those steps for you until the iterator is '
+ 'exhausted.',
+ 'This is the core value-stream protocol in Python: one object produces values, '
+ 'another piece of code consumes them, and many streams are one-pass.'],
+ 'min_python': None,
'notes': ['Iterables produce iterators; iterators produce values.',
'`next()` consumes one value from an iterator.',
'Many iterators are one-pass even when the original collection is reusable.'],
- 'cells': [{'prose': ['`iter()` asks an iterable for an iterator. `next()` consumes one value and advances the '
- "iterator's position."],
- 'code': 'names = ["Ada", "Grace", "Guido"]\n'
- 'iterator = iter(names)\n'
- 'print(next(iterator))\n'
- 'print(next(iterator))',
- 'output': 'Ada\nGrace',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['A `for` loop consumes the same iterator protocol. Because two values were already consumed, '
- 'the loop sees only the remaining value.'],
- 'code': 'for name in iterator:\n print(name)',
- 'output': 'Guido',
- 'line': 33,
- 'kind': 'cell'},
- {'prose': ['The list itself is reusable. Asking it for a fresh iterator starts a new pass over the same '
- 'stored values.'],
- 'code': 'again = iter(names)\nprint(next(again))',
- 'output': 'Ada',
- 'line': 46,
- 'kind': 'cell'}]},
- {'slug': 'sentinel-iteration',
- 'title': 'Sentinel Iteration',
'section': 'Iteration',
- 'summary': 'iter(callable, sentinel) repeats calls until a marker value appears.',
- 'doc_url': 'https://docs.python.org/3.13/library/functions.html#iter',
+ 'see_also': ['iterating-over-iterables', 'iterator-vs-iterable', 'generators'],
+ 'slug': 'iterators',
+ 'summary': 'iter and next expose the protocol behind for loops.',
+ 'title': 'Iterators',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'names = ["Ada", "Grace", "Guido"]\n'
+ 'iterator = iter(names)\n'
+ 'print(next(iterator))\n'
+ 'print(next(iterator))',
+ 'prose': '`iter()` asks an iterable for an iterator. `next()` consumes one '
+ "value and advances the iterator's position."},
+ {'code': 'for name in iterator:\n print(name)',
+ 'prose': 'A `for` loop consumes the same iterator protocol. Because two values '
+ 'were already consumed, the loop sees only the remaining value.'},
+ {'code': 'again = iter(names)\nprint(next(again))',
+ 'prose': 'The list itself is reusable. Asking it for a fresh iterator starts a '
+ 'new pass over the same stored values.'}]},
+ {'cells': [{'code': 'names = ["Ada", "Grace"]\nprint(list(names))\nprint(list(names))',
+ 'kind': 'cell',
+ 'line': 22,
+ 'output': "['Ada', 'Grace']\n['Ada', 'Grace']",
+ 'prose': ['A list is iterable. Each `for` loop or `list()` call asks the list for a '
+ 'fresh iterator under the hood, so the same data can be traversed many '
+ 'times.']},
+ {'code': 'stream = iter(names)\nprint(list(stream))\nprint(list(stream))',
+ 'kind': 'cell',
+ 'line': 37,
+ 'output': "['Ada', 'Grace']\n[]",
+ 'prose': ['An iterator is one-pass. Calling `iter()` returns a position-tracking '
+ 'object; once it has been exhausted, it stays exhausted.']},
+ {'code': 'first = iter(names)\n'
+ 'second = iter(names)\n'
+ 'print(first is second)\n'
+ 'print(iter(first) is first)',
+ 'kind': 'cell',
+ 'line': 52,
+ 'output': 'False\nTrue',
+ 'prose': ['Calling `iter()` on an iterable returns a brand-new iterator each time. '
+ 'Calling `iter()` on an iterator returns the same object — that is the rule '
+ 'that lets a `for` loop accept either kind.']},
+ {'code': 'def total_and_count(numbers):\n'
+ ' total = sum(numbers)\n'
+ ' count = sum(1 for _ in numbers)\n'
+ ' return total, count\n'
+ '\n'
+ 'def values():\n'
+ ' yield from [10, 9, 8]\n'
+ '\n'
+ 'print(total_and_count([10, 9, 8]))\n'
+ 'print(total_and_count(values()))\n'
+ '\n'
+ 'def total_and_count_safe(numbers):\n'
+ ' items = list(numbers)\n'
+ ' return sum(items), len(items)\n'
+ '\n'
+ 'print(total_and_count_safe(values()))',
+ 'kind': 'cell',
+ 'line': 68,
+ 'output': '(27, 3)\n(27, 0)\n(27, 3)',
+ 'prose': ['The distinction shows up at API boundaries. A function that loops over its '
+ 'argument twice works for an iterable but silently produces wrong answers '
+ 'for an iterator, because the second pass finds the iterator already '
+ 'exhausted. Materialize once at the boundary when both passes matter.']}],
+ 'code': 'names = ["Ada", "Grace"]\n'
+ '\n'
+ 'print(list(names))\n'
+ 'print(list(names))\n'
+ '\n'
+ 'stream = iter(names)\n'
+ 'print(list(stream))\n'
+ 'print(list(stream))\n'
+ '\n'
+ 'first = iter(names)\n'
+ 'second = iter(names)\n'
+ 'print(first is second)\n'
+ 'print(iter(first) is first)\n'
+ '\n'
+ 'def total_and_count(numbers):\n'
+ ' total = sum(numbers)\n'
+ ' count = sum(1 for _ in numbers)\n'
+ ' return total, count\n'
+ '\n'
+ 'def values():\n'
+ ' yield from [10, 9, 8]\n'
+ '\n'
+ 'print(total_and_count([10, 9, 8]))\n'
+ 'print(total_and_count(values()))\n'
+ '\n'
+ 'def total_and_count_safe(numbers):\n'
+ ' items = list(numbers)\n'
+ ' return sum(items), len(items)\n'
+ '\n'
+ 'print(total_and_count_safe(values()))\n',
+ 'doc_path': '/glossary.html#term-iterable',
+ 'doc_url': 'https://docs.python.org/3.13/glossary.html#term-iterable',
+ 'expected_output': "['Ada', 'Grace']\n"
+ "['Ada', 'Grace']\n"
+ "['Ada', 'Grace']\n"
+ '[]\n'
+ 'False\n'
+ 'True\n'
+ '(27, 3)\n'
+ '(27, 0)\n'
+ '(27, 3)\n',
+ 'explanation': ['An iterable can produce values when asked. An iterator is the object that '
+ 'remembers where the production currently is. The distinction matters because '
+ 'iterables can be traversed many times, while many iterators can be traversed '
+ 'only once.',
+ '`iter(iterable)` returns a fresh iterator each call. `iter(iterator)` returns '
+ 'the iterator itself. That self-iteration property is how `for` loops can accept '
+ 'either kind, and it is also why a function that loops over its argument twice '
+ 'silently breaks when called with a generator instead of a list.',
+ 'The takeaway for API design: receive iterables when the caller may want a '
+ 'second pass, and materialize once at the boundary if you must.'],
+ 'min_python': None,
+ 'notes': ['An iterable produces an iterator each time `iter()` is called on it; an iterator '
+ 'produces values until it is exhausted.',
+ '`iter(iterable)` returns a fresh iterator; `iter(iterator)` returns the same '
+ 'iterator.',
+ 'Functions that traverse their input more than once must accept an iterable or '
+ 'materialize the input at the boundary.'],
+ 'section': 'Iteration',
+ 'see_also': ['iterators', 'iterating-over-iterables', 'generators'],
+ 'slug': 'iterator-vs-iterable',
+ 'summary': 'Iterables produce fresh iterators; iterators are one-pass.',
+ 'title': 'Iterator vs Iterable',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'names = ["Ada", "Grace"]\nprint(list(names))\nprint(list(names))',
+ 'prose': 'A list is iterable. Each `for` loop or `list()` call asks the list '
+ 'for a fresh iterator under the hood, so the same data can be '
+ 'traversed many times.'},
+ {'code': 'stream = iter(names)\nprint(list(stream))\nprint(list(stream))',
+ 'prose': 'An iterator is one-pass. Calling `iter()` returns a position-tracking '
+ 'object; once it has been exhausted, it stays exhausted.'},
+ {'code': 'first = iter(names)\n'
+ 'second = iter(names)\n'
+ 'print(first is second)\n'
+ 'print(iter(first) is first)',
+ 'prose': 'Calling `iter()` on an iterable returns a brand-new iterator each '
+ 'time. Calling `iter()` on an iterator returns the same object — that '
+ 'is the rule that lets a `for` loop accept either kind.'},
+ {'code': 'def total_and_count(numbers):\n'
+ ' total = sum(numbers)\n'
+ ' count = sum(1 for _ in numbers)\n'
+ ' return total, count\n'
+ '\n'
+ 'def values():\n'
+ ' yield from [10, 9, 8]\n'
+ '\n'
+ 'print(total_and_count([10, 9, 8]))\n'
+ 'print(total_and_count(values()))\n'
+ '\n'
+ 'def total_and_count_safe(numbers):\n'
+ ' items = list(numbers)\n'
+ ' return sum(items), len(items)\n'
+ '\n'
+ 'print(total_and_count_safe(values()))',
+ 'prose': 'The distinction shows up at API boundaries. A function that loops '
+ 'over its argument twice works for an iterable but silently produces '
+ 'wrong answers for an iterator, because the second pass finds the '
+ 'iterator already exhausted. Materialize once at the boundary when '
+ 'both passes matter.'}]},
+ {'cells': [{'code': 'lines = iter(["alpha", "beta", ""])\n'
+ '\n'
+ 'def read_line():\n'
+ ' return next(lines)\n'
+ '\n'
+ 'for line in iter(read_line, ""):\n'
+ ' print(line.upper())',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': 'ALPHA\nBETA',
+ 'prose': ['A zero-argument callable produces one value at a time.']}],
'code': 'lines = iter(["alpha", "beta", ""])\n'
'\n'
'def read_line():\n'
@@ -1050,66 +2210,63 @@
'\n'
'for line in iter(read_line, ""):\n'
' print(line.upper())\n',
+ 'doc_path': '/library/functions.html#iter',
+ 'doc_url': 'https://docs.python.org/3.13/library/functions.html#iter',
'expected_output': 'ALPHA\nBETA\n',
+ 'explanation': ['`iter(callable, sentinel)` keeps calling a zero-argument callable and yields '
+ 'each result until the callable returns the sentinel value. It is the right '
+ 'shape for repeated reads from files, sockets, or queues — sources where each '
+ 'call produces the next chunk and a known marker means "no more".',
+ 'Reach for it instead of writing `while True:` plus a manual break when the loop '
+ 'body would do nothing else but read and check. The two-argument form turns a '
+ 'polling callable into something that composes with `for` loops, comprehensions, '
+ 'and other iterator helpers.',
+ 'The callable must take no arguments. Wrap a parameterized reader in a small '
+ 'lambda or method that closes over the parameters when the underlying API needs '
+ 'them.'],
+ 'min_python': None,
'notes': ['A zero-argument callable produces one value at a time.',
'The sentinel value stops the loop without appearing in the output.',
'This form is useful for repeated reads from files, sockets, or queues.'],
- 'cells': [{'prose': ['A zero-argument callable produces one value at a time.'],
- 'code': 'lines = iter(["alpha", "beta", ""])\n'
- '\n'
- 'def read_line():\n'
- ' return next(lines)\n'
- '\n'
- 'for line in iter(read_line, ""):\n'
- ' print(line.upper())',
- 'output': 'ALPHA\nBETA',
- 'line': 17,
- 'kind': 'cell'}]},
- {'slug': 'match-statements',
- 'title': 'Match Statements',
- 'section': 'Control Flow',
- 'summary': 'match selects cases using structural pattern matching.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#match-statements',
- 'code': 'command = {"action": "move", "x": 3, "y": 4}\n'
- '\n'
- 'match command:\n'
- ' case {"action": "move", "x": x, "y": y}:\n'
- ' print(f"move to {x},{y}")\n'
- ' case {"action": "quit"}:\n'
- ' print("quit")\n'
- ' case {"action": action}:\n'
- ' print(f"unknown action: {action}")\n'
- ' case _:\n'
- ' print("invalid command")\n',
- 'expected_output': 'move to 3,4\n',
- 'notes': ['`match` compares structure, not just equality.',
- 'Patterns can bind names such as `x` and `y` while matching.',
- 'Put the catch-all `_` case last, because cases are tried from top to bottom.'],
- 'cells': [{'prose': ['Use `match` when the shape of a value is the decision. This command is a dictionary with an '
- 'action and coordinates; the first case checks that shape and binds `x` and `y`.'],
- 'code': 'command = {"action": "move", "x": 3, "y": 4}\n'
+ 'section': 'Iteration',
+ 'see_also': [],
+ 'slug': 'sentinel-iteration',
+ 'summary': 'iter(callable, sentinel) repeats calls until a marker value appears.',
+ 'title': 'Sentinel Iteration',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'lines = iter(["alpha", "beta", ""])\n'
+ '\n'
+ 'def read_line():\n'
+ ' return next(lines)\n'
+ '\n'
+ 'for line in iter(read_line, ""):\n'
+ ' print(line.upper())',
+ 'prose': 'A zero-argument callable produces one value at a time.'}]},
+ {'cells': [{'code': 'command = {"action": "move", "x": 3, "y": 4}\n'
'\n'
'match command:\n'
' case {"action": "move", "x": x, "y": y}:\n'
' print(f"move to {x},{y}")',
- 'output': 'move to 3,4',
+ 'kind': 'cell',
'line': 17,
- 'kind': 'cell'},
- {'prose': ['Other cases describe other valid shapes. This complete fragment changes the command so the '
- '`quit` case is the first matching pattern.'],
- 'code': 'command = {"action": "quit"}\n'
+ 'output': 'move to 3,4',
+ 'prose': ['Use `match` when the shape of a value is the decision. This command is a '
+ 'dictionary with an action and coordinates; the first case checks that '
+ 'shape and binds `x` and `y`.']},
+ {'code': 'command = {"action": "quit"}\n'
'\n'
'match command:\n'
' case {"action": "move", "x": x, "y": y}:\n'
' print(f"move to {x},{y}")\n'
' case {"action": "quit"}:\n'
' print("quit")',
- 'output': 'quit',
+ 'kind': 'cell',
'line': 33,
- 'kind': 'cell'},
- {'prose': ['Broader patterns and the `_` catch-all belong after specific cases. This fragment extracts an '
- 'unknown action before the final fallback would run.'],
- 'code': 'command = {"action": "jump"}\n'
+ 'output': 'quit',
+ 'prose': ['Other cases describe other valid shapes. This complete fragment changes '
+ 'the command so the `quit` case is the first matching pattern.']},
+ {'code': 'command = {"action": "jump"}\n'
'\n'
'match command:\n'
' case {"action": "move", "x": x, "y": y}:\n'
@@ -1120,14 +2277,105 @@
' print(f"unknown action: {action}")\n'
' case _:\n'
' print("invalid command")',
- 'output': 'unknown action: jump',
+ 'kind': 'cell',
'line': 51,
- 'kind': 'cell'}]},
- {'slug': 'advanced-match-patterns',
- 'title': 'Advanced Match Patterns',
- 'section': 'Control Flow',
- 'summary': 'match patterns can destructure sequences, combine alternatives, and add guards.',
+ 'output': 'unknown action: jump',
+ 'prose': ['Broader patterns and the `_` catch-all belong after specific cases. This '
+ 'fragment extracts an unknown action before the final fallback would '
+ 'run.']}],
+ 'code': 'command = {"action": "move", "x": 3, "y": 4}\n'
+ '\n'
+ 'match command:\n'
+ ' case {"action": "move", "x": x, "y": y}:\n'
+ ' print(f"move to {x},{y}")\n'
+ ' case {"action": "quit"}:\n'
+ ' print("quit")\n'
+ ' case {"action": action}:\n'
+ ' print(f"unknown action: {action}")\n'
+ ' case _:\n'
+ ' print("invalid command")\n',
+ 'doc_path': '/tutorial/controlflow.html#match-statements',
'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#match-statements',
+ 'expected_output': 'move to 3,4\n',
+ 'explanation': ['Structural pattern matching lets a program choose a branch based on the shape '
+ 'of data. It is especially useful when commands, messages, or parsed data have a '
+ 'few known forms.',
+ 'A `case` pattern can both check constants and bind names. The move case checks '
+ 'the action and extracts `x` and `y` in one readable step.',
+ 'Order matters because Python tries cases from top to bottom. Specific shapes '
+ 'should appear before broad fallback cases such as `_`.'],
+ 'min_python': None,
+ 'notes': ['`match` compares structure, not just equality.',
+ 'Patterns can bind names such as `x` and `y` while matching.',
+ 'Put the catch-all `_` case last, because cases are tried from top to bottom.'],
+ 'section': 'Control Flow',
+ 'see_also': [],
+ 'slug': 'match-statements',
+ 'summary': 'match selects cases using structural pattern matching.',
+ 'title': 'Match Statements',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'command = {"action": "move", "x": 3, "y": 4}\n'
+ '\n'
+ 'match command:\n'
+ ' case {"action": "move", "x": x, "y": y}:\n'
+ ' print(f"move to {x},{y}")',
+ 'prose': 'Use `match` when the shape of a value is the decision. This command '
+ 'is a dictionary with an action and coordinates; the first case checks '
+ 'that shape and binds `x` and `y`.'},
+ {'code': 'command = {"action": "quit"}\n'
+ '\n'
+ 'match command:\n'
+ ' case {"action": "move", "x": x, "y": y}:\n'
+ ' print(f"move to {x},{y}")\n'
+ ' case {"action": "quit"}:\n'
+ ' print("quit")',
+ 'prose': 'Other cases describe other valid shapes. This complete fragment '
+ 'changes the command so the `quit` case is the first matching '
+ 'pattern.'},
+ {'code': 'command = {"action": "jump"}\n'
+ '\n'
+ 'match command:\n'
+ ' case {"action": "move", "x": x, "y": y}:\n'
+ ' print(f"move to {x},{y}")\n'
+ ' case {"action": "quit"}:\n'
+ ' print("quit")\n'
+ ' case {"action": action}:\n'
+ ' print(f"unknown action: {action}")\n'
+ ' case _:\n'
+ ' print("invalid command")',
+ 'prose': 'Broader patterns and the `_` catch-all belong after specific cases. '
+ 'This fragment extracts an unknown action before the final fallback '
+ 'would run.'}]},
+ {'cells': [{'code': 'def describe(command):\n'
+ ' match command:\n'
+ ' case ["move", x, y] if x >= 0 and y >= 0:\n'
+ ' return f"move to {x},{y}"\n'
+ ' case ["quit" | "exit"]:\n'
+ ' return "stop"\n'
+ ' case ["echo", *words]:\n'
+ ' return " ".join(words)\n'
+ ' case _:\n'
+ ' return "unknown"\n'
+ '\n'
+ 'print(describe(["move", 2, 3]))',
+ 'kind': 'cell',
+ 'line': 22,
+ 'output': 'move to 2,3',
+ 'prose': ['Sequence patterns match by position. A guard after `if` adds a condition '
+ 'that must also be true.']},
+ {'code': 'print(describe(["exit"]))\nprint(describe(["echo", "hello", "python"]))',
+ 'kind': 'cell',
+ 'line': 45,
+ 'output': 'stop\nhello python',
+ 'prose': ['An OR pattern accepts several alternatives in one case. A star pattern '
+ 'captures the rest of a sequence.']},
+ {'code': 'print(describe(["move", -1, 3]))',
+ 'kind': 'cell',
+ 'line': 59,
+ 'output': 'unknown',
+ 'prose': ['The wildcard `_` catches values that did not match earlier cases. Here the '
+ 'guard rejects the negative coordinate.']}],
'code': 'def describe(command):\n'
' match command:\n'
' case ["move", x, y] if x >= 0 and y >= 0:\n'
@@ -1143,44 +2391,70 @@
'print(describe(["exit"]))\n'
'print(describe(["echo", "hello", "python"]))\n'
'print(describe(["move", -1, 3]))\n',
+ 'doc_path': '/tutorial/controlflow.html#match-statements',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#match-statements',
'expected_output': 'move to 2,3\nstop\nhello python\nunknown\n',
+ 'explanation': ['Structural pattern matching is more than equality checks. Patterns can '
+ 'destructure sequences, match several alternatives, capture the rest of a '
+ 'sequence, and use guards.',
+ 'Use these forms when the shape of data is the decision. If the decision is only '
+ 'a single boolean condition, ordinary `if` statements are usually clearer.',
+ 'The wildcard `_` catches everything not matched earlier.'],
+ 'min_python': None,
'notes': ['Use `case _` as a wildcard fallback.',
'Guards refine a pattern after the structure matches.',
'OR patterns and star patterns keep shape-based branches compact.'],
- 'cells': [{'prose': ['Sequence patterns match by position. A guard after `if` adds a condition that must also be '
- 'true.'],
- 'code': 'def describe(command):\n'
- ' match command:\n'
- ' case ["move", x, y] if x >= 0 and y >= 0:\n'
- ' return f"move to {x},{y}"\n'
- ' case ["quit" | "exit"]:\n'
- ' return "stop"\n'
- ' case ["echo", *words]:\n'
- ' return " ".join(words)\n'
- ' case _:\n'
- ' return "unknown"\n'
- '\n'
- 'print(describe(["move", 2, 3]))',
- 'output': 'move to 2,3',
- 'line': 22,
- 'kind': 'cell'},
- {'prose': ['An OR pattern accepts several alternatives in one case. A star pattern captures the rest of a '
- 'sequence.'],
- 'code': 'print(describe(["exit"]))\nprint(describe(["echo", "hello", "python"]))',
- 'output': 'stop\nhello python',
- 'line': 45,
- 'kind': 'cell'},
- {'prose': ['The wildcard `_` catches values that did not match earlier cases. Here the guard rejects the '
- 'negative coordinate.'],
- 'code': 'print(describe(["move", -1, 3]))',
- 'output': 'unknown',
- 'line': 59,
- 'kind': 'cell'}]},
- {'slug': 'while-loops',
- 'title': 'While Loops',
'section': 'Control Flow',
- 'summary': 'while repeats until changing state makes a condition false.',
- 'doc_url': 'https://docs.python.org/3.13/reference/compound_stmts.html#while',
+ 'see_also': ['match-statements', 'tuples', 'classes'],
+ 'slug': 'advanced-match-patterns',
+ 'summary': 'match patterns can destructure sequences, combine alternatives, and add guards.',
+ 'title': 'Advanced Match Patterns',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'def describe(command):\n'
+ ' match command:\n'
+ ' case ["move", x, y] if x >= 0 and y >= 0:\n'
+ ' return f"move to {x},{y}"\n'
+ ' case ["quit" | "exit"]:\n'
+ ' return "stop"\n'
+ ' case ["echo", *words]:\n'
+ ' return " ".join(words)\n'
+ ' case _:\n'
+ ' return "unknown"\n'
+ '\n'
+ 'print(describe(["move", 2, 3]))',
+ 'prose': 'Sequence patterns match by position. A guard after `if` adds a '
+ 'condition that must also be true.'},
+ {'code': 'print(describe(["exit"]))\n'
+ 'print(describe(["echo", "hello", "python"]))',
+ 'prose': 'An OR pattern accepts several alternatives in one case. A star '
+ 'pattern captures the rest of a sequence.'},
+ {'code': 'print(describe(["move", -1, 3]))',
+ 'prose': 'The wildcard `_` catches values that did not match earlier cases. '
+ 'Here the guard rejects the negative coordinate.'}]},
+ {'cells': [{'code': 'remaining = 3\n'
+ 'while remaining > 0:\n'
+ ' print(f"launch in {remaining}")\n'
+ ' remaining -= 1\n'
+ 'print("liftoff")',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': 'launch in 3\nlaunch in 2\nlaunch in 1\nliftoff',
+ 'prose': ['Use `while` when the condition, not an iterable, controls repetition. Here '
+ 'the loop owns the countdown state and updates it each time through the '
+ 'body.']},
+ {'code': 'responses = iter(["retry", "retry", "ok"])\n'
+ 'status = next(responses)\n'
+ 'while status != "ok":\n'
+ ' print(f"status: {status}")\n'
+ ' status = next(responses)\n'
+ 'print(f"status: {status}")',
+ 'kind': 'cell',
+ 'line': 36,
+ 'output': 'status: retry\nstatus: retry\nstatus: ok',
+ 'prose': ['A sentinel loop stops when a special value appears. The loop does not know '
+ 'in advance how many retries it will need; it keeps going until the state '
+ 'says to stop.']}],
'code': 'remaining = 3\n'
'while remaining > 0:\n'
' print(f"launch in {remaining}")\n'
@@ -1193,36 +2467,70 @@
' print(f"status: {status}")\n'
' status = next(responses)\n'
'print(f"status: {status}")\n',
- 'expected_output': 'launch in 3\nlaunch in 2\nlaunch in 1\nliftoff\nstatus: retry\nstatus: retry\nstatus: ok\n',
+ 'doc_path': '/reference/compound_stmts.html#while',
+ 'doc_url': 'https://docs.python.org/3.13/reference/compound_stmts.html#while',
+ 'expected_output': 'launch in 3\n'
+ 'launch in 2\n'
+ 'launch in 1\n'
+ 'liftoff\n'
+ 'status: retry\n'
+ 'status: retry\n'
+ 'status: ok\n',
+ 'explanation': ['A `while` loop repeats while a condition remains true. Unlike `for`, which '
+ 'consumes an existing iterable, `while` is for state-driven repetition where the '
+ 'next step depends on what happened so far.',
+ 'The loop body must make progress toward stopping. That progress might be '
+ 'decrementing a counter, reading until a sentinel value, or waiting until some '
+ 'external state changes.',
+ 'Reach for `for` when you already have values to consume. Reach for `while` when '
+ "the loop's own state decides whether another iteration is needed."],
+ 'min_python': None,
'notes': ['Use `while` when changing state decides whether the loop continues.',
'Update loop state inside the body so the condition can become false.',
- 'Prefer `for` when you already have a collection, range, iterator, or generator to consume.'],
- 'cells': [{'prose': ['Use `while` when the condition, not an iterable, controls repetition. Here the loop owns the '
- 'countdown state and updates it each time through the body.'],
- 'code': 'remaining = 3\n'
- 'while remaining > 0:\n'
- ' print(f"launch in {remaining}")\n'
- ' remaining -= 1\n'
- 'print("liftoff")',
- 'output': 'launch in 3\nlaunch in 2\nlaunch in 1\nliftoff',
+ 'Prefer `for` when you already have a collection, range, iterator, or generator to '
+ 'consume.'],
+ 'section': 'Control Flow',
+ 'see_also': [],
+ 'slug': 'while-loops',
+ 'summary': 'while repeats until changing state makes a condition false.',
+ 'title': 'While Loops',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'remaining = 3\n'
+ 'while remaining > 0:\n'
+ ' print(f"launch in {remaining}")\n'
+ ' remaining -= 1\n'
+ 'print("liftoff")',
+ 'prose': 'Use `while` when the condition, not an iterable, controls repetition. '
+ 'Here the loop owns the countdown state and updates it each time '
+ 'through the body.'},
+ {'code': 'responses = iter(["retry", "retry", "ok"])\n'
+ 'status = next(responses)\n'
+ 'while status != "ok":\n'
+ ' print(f"status: {status}")\n'
+ ' status = next(responses)\n'
+ 'print(f"status: {status}")',
+ 'prose': 'A sentinel loop stops when a special value appears. The loop does not '
+ 'know in advance how many retries it will need; it keeps going until '
+ 'the state says to stop.'}]},
+ {'cells': [{'code': 'numbers = [3, 1, 4]\nnumbers.append(1)\n\nprint(numbers)',
+ 'kind': 'cell',
'line': 17,
- 'kind': 'cell'},
- {'prose': ['A sentinel loop stops when a special value appears. The loop does not know in advance how many '
- 'retries it will need; it keeps going until the state says to stop.'],
- 'code': 'responses = iter(["retry", "retry", "ok"])\n'
- 'status = next(responses)\n'
- 'while status != "ok":\n'
- ' print(f"status: {status}")\n'
- ' status = next(responses)\n'
- 'print(f"status: {status}")',
- 'output': 'status: retry\nstatus: retry\nstatus: ok',
- 'line': 36,
- 'kind': 'cell'}]},
- {'slug': 'lists',
- 'title': 'Lists',
- 'section': 'Collections',
- 'summary': 'Lists are ordered, mutable collections.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/datastructures.html#more-on-lists',
+ 'output': '[3, 1, 4, 1]',
+ 'prose': ['Create a list with square brackets. Because lists are mutable, `append()` '
+ 'changes this same list object.']},
+ {'code': 'print(numbers[0])\nprint(numbers[-1])',
+ 'kind': 'cell',
+ 'line': 32,
+ 'output': '3\n1',
+ 'prose': ['Use indexes to read positions. Negative indexes are convenient for reading '
+ 'from the end.']},
+ {'code': 'print(sorted(numbers))\nprint(numbers)',
+ 'kind': 'cell',
+ 'line': 46,
+ 'output': '[1, 1, 3, 4]\n[3, 1, 4, 1]',
+ 'prose': ['Use `sorted()` when you want an ordered copy and still need the original '
+ 'order afterward.']}],
'code': 'numbers = [3, 1, 4]\n'
'numbers.append(1)\n'
'\n'
@@ -1231,31 +2539,68 @@
'print(numbers[-1])\n'
'print(sorted(numbers))\n'
'print(numbers)\n',
+ 'doc_path': '/tutorial/datastructures.html#more-on-lists',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/datastructures.html#more-on-lists',
'expected_output': '[3, 1, 4, 1]\n3\n1\n[1, 1, 3, 4]\n[3, 1, 4, 1]\n',
+ 'explanation': ["Lists are Python's general-purpose mutable sequence type. Use them when order "
+ 'matters and the collection may grow, shrink, or be rearranged.',
+ 'Indexing reads individual positions. `0` is the first item, and negative '
+ 'indexes count backward from the end.',
+ 'Mutation and copying matter: `append()` changes the list, while `sorted()` '
+ 'returns a new ordered list and leaves the original alone.'],
+ 'min_python': None,
'notes': ['Lists are mutable sequences: methods such as `append()` change the list in place.',
'Negative indexes count from the end.',
'`sorted()` returns a new list; `list.sort()` sorts the existing list in place.'],
- 'cells': [{'prose': ['Create a list with square brackets. Because lists are mutable, `append()` changes this same '
- 'list object.'],
- 'code': 'numbers = [3, 1, 4]\nnumbers.append(1)\n\nprint(numbers)',
- 'output': '[3, 1, 4, 1]',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['Use indexes to read positions. Negative indexes are convenient for reading from the end.'],
- 'code': 'print(numbers[0])\nprint(numbers[-1])',
- 'output': '3\n1',
- 'line': 32,
- 'kind': 'cell'},
- {'prose': ['Use `sorted()` when you want an ordered copy and still need the original order afterward.'],
- 'code': 'print(sorted(numbers))\nprint(numbers)',
- 'output': '[1, 1, 3, 4]\n[3, 1, 4, 1]',
- 'line': 46,
- 'kind': 'cell'}]},
- {'slug': 'tuples',
- 'title': 'Tuples',
'section': 'Collections',
- 'summary': 'Tuples group a fixed number of positional values.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/datastructures.html#tuples-and-sequences',
+ 'see_also': [],
+ 'slug': 'lists',
+ 'summary': 'Lists are ordered, mutable collections.',
+ 'title': 'Lists',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'numbers = [3, 1, 4]\nnumbers.append(1)\n\nprint(numbers)',
+ 'prose': 'Create a list with square brackets. Because lists are mutable, '
+ '`append()` changes this same list object.'},
+ {'code': 'print(numbers[0])\nprint(numbers[-1])',
+ 'prose': 'Use indexes to read positions. Negative indexes are convenient for '
+ 'reading from the end.'},
+ {'code': 'print(sorted(numbers))\nprint(numbers)',
+ 'prose': 'Use `sorted()` when you want an ordered copy and still need the '
+ 'original order afterward.'}]},
+ {'cells': [{'code': 'point = (3, 4)\nx, y = point\nprint(x + y)',
+ 'kind': 'cell',
+ 'line': 22,
+ 'output': '7',
+ 'prose': ['Use a tuple for a fixed-size record where each position has a known '
+ 'meaning. Unpacking turns those positions into names at the point of use.']},
+ {'code': 'red = (255, 0, 0)\nprint(red[0])\nprint(len(red))',
+ 'kind': 'cell',
+ 'line': 36,
+ 'output': '255\n3',
+ 'prose': ['Tuples are sequences, so indexing and `len()` work. They are different '
+ 'from lists because their length and item references are fixed after '
+ 'creation.']},
+ {'code': 'record = ("Ada", 10)\nname, score = record\nprint(f"{name}: {score}")',
+ 'kind': 'cell',
+ 'line': 51,
+ 'output': 'Ada: 10',
+ 'prose': ['Tuples pair naturally with multiple return values and unpacking. If the '
+ 'fields need names everywhere, graduate to a dataclass or named tuple.']},
+ {'code': 'scores = [10, 9, 8]\n'
+ 'scores.append(7)\n'
+ 'print(scores)\n'
+ '\n'
+ 'student = ("Ada", 2024, "math")\n'
+ 'name, year, subject = student\n'
+ 'print(name, year, subject)',
+ 'kind': 'cell',
+ 'line': 65,
+ 'output': '[10, 9, 8, 7]\nAda 2024 math',
+ 'prose': ['Lists and tuples carry different intent. A list holds a variable number of '
+ 'similar items and grows with `append`; a tuple has a fixed shape where '
+ 'each position has its own meaning, and unpacking gives those positions '
+ 'names.']}],
'code': 'point = (3, 4)\n'
'x, y = point\n'
'print(x + y)\n'
@@ -1266,34 +2611,89 @@
'\n'
'record = ("Ada", 10)\n'
'name, score = record\n'
- 'print(f"{name}: {score}")\n',
- 'expected_output': '7\n255\n3\nAda: 10\n',
+ 'print(f"{name}: {score}")\n'
+ '\n'
+ 'scores = [10, 9, 8]\n'
+ 'scores.append(7)\n'
+ 'print(scores)\n'
+ '\n'
+ 'student = ("Ada", 2024, "math")\n'
+ 'name, year, subject = student\n'
+ 'print(name, year, subject)\n',
+ 'doc_path': '/tutorial/datastructures.html#tuples-and-sequences',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/datastructures.html#tuples-and-sequences',
+ 'expected_output': '7\n255\n3\nAda: 10\n[10, 9, 8, 7]\nAda 2024 math\n',
+ 'explanation': ['Tuples are ordered, immutable sequences. They exist for small fixed groups '
+ 'where position has meaning: coordinates, RGB colors, database rows, and '
+ 'multiple return values.',
+ 'Use lists for variable-length collections of similar items. Use tuples when the '
+ 'number of positions is part of the data shape and unpacking can give each '
+ 'position a useful name.',
+ 'Because tuples are immutable, you cannot append or replace positions in place. '
+ 'If the shape needs to grow or change, a list or dataclass is usually a better '
+ 'fit.'],
+ 'min_python': None,
'notes': ['Tuples are immutable sequences with fixed length.',
'Use tuples for small records where position has meaning.',
- 'Use lists for variable-length collections of similar items.'],
- 'cells': [{'prose': ['Use a tuple for a fixed-size record where each position has a known meaning. Unpacking turns '
- 'those positions into names at the point of use.'],
- 'code': 'point = (3, 4)\nx, y = point\nprint(x + y)',
- 'output': '7',
+ 'Use lists for variable-length collections of similar items.',
+ "Reach for a dataclass or `NamedTuple` when fields deserve names everywhere they're "
+ 'used.'],
+ 'section': 'Collections',
+ 'see_also': ['lists', 'unpacking', 'structured-data-shapes'],
+ 'slug': 'tuples',
+ 'summary': 'Tuples group a fixed number of positional values.',
+ 'title': 'Tuples',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'point = (3, 4)\nx, y = point\nprint(x + y)',
+ 'prose': 'Use a tuple for a fixed-size record where each position has a known '
+ 'meaning. Unpacking turns those positions into names at the point of '
+ 'use.'},
+ {'code': 'red = (255, 0, 0)\nprint(red[0])\nprint(len(red))',
+ 'prose': 'Tuples are sequences, so indexing and `len()` work. They are '
+ 'different from lists because their length and item references are '
+ 'fixed after creation.'},
+ {'code': 'record = ("Ada", 10)\nname, score = record\nprint(f"{name}: {score}")',
+ 'prose': 'Tuples pair naturally with multiple return values and unpacking. If '
+ 'the fields need names everywhere, graduate to a dataclass or named '
+ 'tuple.'},
+ {'code': 'scores = [10, 9, 8]\n'
+ 'scores.append(7)\n'
+ 'print(scores)\n'
+ '\n'
+ 'student = ("Ada", 2024, "math")\n'
+ 'name, year, subject = student\n'
+ 'print(name, year, subject)',
+ 'prose': 'Lists and tuples carry different intent. A list holds a variable '
+ 'number of similar items and grows with `append`; a tuple has a fixed '
+ 'shape where each position has its own meaning, and unpacking gives '
+ 'those positions names.'}]},
+ {'cells': [{'code': 'point = (3, 4)\nx, y = point\nprint(x, y)',
+ 'kind': 'cell',
'line': 17,
- 'kind': 'cell'},
- {'prose': ['Tuples are sequences, so indexing and `len()` work. They are different from lists because '
- 'their length and item references are fixed after creation.'],
- 'code': 'red = (255, 0, 0)\nprint(red[0])\nprint(len(red))',
- 'output': '255\n3',
+ 'output': '3 4',
+ 'prose': ['Unpacking binds multiple names from one iterable or mapping. It makes the '
+ 'structure of data visible at the point where values are introduced.']},
+ {'code': 'first, *middle, last = [1, 2, 3, 4]\nprint(first, middle, last)',
+ 'kind': 'cell',
'line': 31,
- 'kind': 'cell'},
- {'prose': ['Tuples pair naturally with multiple return values and unpacking. If the fields need names '
- 'everywhere, graduate to a dataclass or named tuple.'],
- 'code': 'record = ("Ada", 10)\nname, score = record\nprint(f"{name}: {score}")',
- 'output': 'Ada: 10',
- 'line': 46,
- 'kind': 'cell'}]},
- {'slug': 'unpacking',
- 'title': 'Unpacking',
- 'section': 'Collections',
- 'summary': 'Unpacking binds names from sequences and mappings concisely.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/datastructures.html#tuples-and-sequences',
+ 'output': '1 [2, 3] 4',
+ 'prose': ['Starred unpacking handles variable-length sequences by collecting the '
+ 'middle or remaining values. This keeps common head-tail patterns '
+ 'readable.']},
+ {'code': 'def describe(name, language):\n'
+ ' print(name, language)\n'
+ '\n'
+ 'data = {"name": "Ada", "language": "Python"}\n'
+ 'describe(**data)',
+ 'kind': 'cell',
+ 'line': 44,
+ 'output': 'Ada Python',
+ 'prose': ['Dictionary unpacking with ** connects structured data to function calls. '
+ 'It is widely used in configuration, adapters, and code that bridges APIs.',
+ 'Dictionary unpacking with ** connects structured data to function calls. '
+ 'It is widely used in configuration, adapters, and code that bridges '
+ 'APIs.']}],
'code': 'point = (3, 4)\n'
'x, y = point\n'
'print(x, y)\n'
@@ -1306,40 +2706,86 @@
'\n'
'data = {"name": "Ada", "language": "Python"}\n'
'describe(**data)\n',
+ 'doc_path': '/tutorial/datastructures.html#tuples-and-sequences',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/datastructures.html#tuples-and-sequences',
'expected_output': '3 4\n1 [2, 3] 4\nAda Python\n',
+ 'explanation': ['Unpacking binds multiple names from one iterable or mapping. It makes the '
+ 'structure of data visible at the point where values are introduced.',
+ 'Starred unpacking handles variable-length sequences by collecting the middle or '
+ 'remaining values. This keeps common head-tail patterns readable.',
+ 'Dictionary unpacking with ** connects structured data to function calls. It is '
+ 'widely used in configuration, adapters, and code that bridges APIs.'],
+ 'min_python': None,
'notes': ['Starred unpacking collects the remaining values into a list.',
'Dictionary unpacking with ** is common when calling functions with structured data.',
- 'Prefer indexing when you need one position; prefer unpacking when naming several positions makes the '
- 'shape clearer.'],
- 'cells': [{'prose': ['Unpacking binds multiple names from one iterable or mapping. It makes the structure of data '
- 'visible at the point where values are introduced.'],
- 'code': 'point = (3, 4)\nx, y = point\nprint(x, y)',
- 'output': '3 4',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['Starred unpacking handles variable-length sequences by collecting the middle or remaining '
- 'values. This keeps common head-tail patterns readable.'],
- 'code': 'first, *middle, last = [1, 2, 3, 4]\nprint(first, middle, last)',
- 'output': '1 [2, 3] 4',
- 'line': 31,
- 'kind': 'cell'},
- {'prose': ['Dictionary unpacking with ** connects structured data to function calls. It is widely used in '
- 'configuration, adapters, and code that bridges APIs.',
- 'Dictionary unpacking with ** connects structured data to function calls. It is widely used in '
- 'configuration, adapters, and code that bridges APIs.'],
- 'code': 'def describe(name, language):\n'
- ' print(name, language)\n'
- '\n'
- 'data = {"name": "Ada", "language": "Python"}\n'
- 'describe(**data)',
- 'output': 'Ada Python',
- 'line': 44,
- 'kind': 'cell'}]},
- {'slug': 'dicts',
- 'title': 'Dictionaries',
+ 'Prefer indexing when you need one position; prefer unpacking when naming several '
+ 'positions makes the shape clearer.'],
'section': 'Collections',
- 'summary': 'Dictionaries map keys to values for records, lookup, and structured data.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/datastructures.html#dictionaries',
+ 'see_also': [],
+ 'slug': 'unpacking',
+ 'summary': 'Unpacking binds names from sequences and mappings concisely.',
+ 'title': 'Unpacking',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'point = (3, 4)\nx, y = point\nprint(x, y)',
+ 'prose': 'Unpacking binds multiple names from one iterable or mapping. It makes '
+ 'the structure of data visible at the point where values are '
+ 'introduced.'},
+ {'code': 'first, *middle, last = [1, 2, 3, 4]\nprint(first, middle, last)',
+ 'prose': 'Starred unpacking handles variable-length sequences by collecting the '
+ 'middle or remaining values. This keeps common head-tail patterns '
+ 'readable.'},
+ {'code': 'def describe(name, language):\n'
+ ' print(name, language)\n'
+ '\n'
+ 'data = {"name": "Ada", "language": "Python"}\n'
+ 'describe(**data)',
+ 'prose': 'Dictionary unpacking with ** connects structured data to function '
+ 'calls. It is widely used in configuration, adapters, and code that '
+ 'bridges APIs.'},
+ {'code': 'def describe(name, language):\n'
+ ' print(name, language)\n'
+ '\n'
+ 'data = {"name": "Ada", "language": "Python"}\n'
+ 'describe(**data)',
+ 'prose': 'Dictionary unpacking with ** connects structured data to function '
+ 'calls. It is widely used in configuration, adapters, and code that '
+ 'bridges APIs.'}]},
+ {'cells': [{'code': 'profile = {"name": "Ada", "language": "Python"}\n'
+ 'profile["year"] = 1843\n'
+ 'print(profile["name"])\n'
+ 'print(profile.get("timezone", "UTC"))',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': 'Ada\nUTC',
+ 'prose': ['Use a dictionary as a small record when fields have names. Direct indexing '
+ 'communicates that the key is required, while `get()` communicates that a '
+ 'missing key has a fallback.']},
+ {'code': 'scores = {"Ada": 10, "Grace": 9}\n'
+ 'print(scores["Grace"])\n'
+ 'print(scores.get("Guido", 0))',
+ 'kind': 'cell',
+ 'line': 33,
+ 'output': '9\n0',
+ 'prose': ['Use a dictionary as a lookup table when keys identify values. This is '
+ 'different from a list, where numeric position is the lookup key.']},
+ {'code': 'for name, score in scores.items():\n print(f"{name}: {score}")',
+ 'kind': 'cell',
+ 'line': 48,
+ 'output': 'Ada: 10\nGrace: 9',
+ 'prose': ['Use `items()` when the loop needs both keys and values. It avoids looping '
+ 'over keys and then indexing back into the dictionary.']},
+ {'code': 'inventory = {"apple": 0, "pear": 3, "plum": 0}\n'
+ 'for name in list(inventory.keys()):\n'
+ ' if inventory[name] == 0:\n'
+ ' del inventory[name]\n'
+ 'print(inventory)',
+ 'kind': 'cell',
+ 'line': 62,
+ 'output': "{'pear': 3}",
+ 'prose': ['Mutating a dictionary while iterating it raises `RuntimeError`. Snapshot '
+ 'the keys with `list(d.keys())` (or build a list of changes and apply them '
+ 'after the loop) so the iteration sees a stable view.']}],
'code': 'profile = {"name": "Ada", "language": "Python"}\n'
'profile["year"] = 1843\n'
'print(profile["name"])\n'
@@ -1350,37 +2796,86 @@
'print(scores.get("Guido", 0))\n'
'\n'
'for name, score in scores.items():\n'
- ' print(f"{name}: {score}")\n',
- 'expected_output': 'Ada\nUTC\n9\n0\nAda: 10\nGrace: 9\n',
+ ' print(f"{name}: {score}")\n'
+ '\n'
+ 'inventory = {"apple": 0, "pear": 3, "plum": 0}\n'
+ 'for name in list(inventory.keys()):\n'
+ ' if inventory[name] == 0:\n'
+ ' del inventory[name]\n'
+ 'print(inventory)\n',
+ 'doc_path': '/tutorial/datastructures.html#dictionaries',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/datastructures.html#dictionaries',
+ 'expected_output': "Ada\nUTC\n9\n0\nAda: 10\nGrace: 9\n{'pear': 3}\n",
+ 'explanation': ["Dictionaries are Python's built-in mapping type. They exist for data where "
+ 'names or keys are more meaningful than numeric positions: records, lookup '
+ 'tables, counters, and JSON-like payloads.',
+ 'Use direct indexing when a key is required. Use `get()` when absence is '
+ 'expected and the code has a reasonable fallback.',
+ 'Unlike lists, dictionaries answer “what value belongs to this key?” rather than '
+ '“what value is at this position?” Iterating with `items()` keeps each key next '
+ 'to its value.'],
+ 'min_python': None,
'notes': ['Dictionaries preserve insertion order in modern Python.',
'Use `get()` when a missing key has a reasonable default.',
- 'Use direct indexing when a missing key should be treated as an error.'],
- 'cells': [{'prose': ['Use a dictionary as a small record when fields have names. Direct indexing communicates that '
- 'the key is required, while `get()` communicates that a missing key has a fallback.'],
- 'code': 'profile = {"name": "Ada", "language": "Python"}\n'
- 'profile["year"] = 1843\n'
- 'print(profile["name"])\n'
- 'print(profile.get("timezone", "UTC"))',
- 'output': 'Ada\nUTC',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['Use a dictionary as a lookup table when keys identify values. This is different from a list, '
- 'where numeric position is the lookup key.'],
- 'code': 'scores = {"Ada": 10, "Grace": 9}\nprint(scores["Grace"])\nprint(scores.get("Guido", 0))',
- 'output': '9\n0',
- 'line': 33,
- 'kind': 'cell'},
- {'prose': ['Use `items()` when the loop needs both keys and values. It avoids looping over keys and then '
- 'indexing back into the dictionary.'],
- 'code': 'for name, score in scores.items():\n print(f"{name}: {score}")',
- 'output': 'Ada: 10\nGrace: 9',
- 'line': 48,
- 'kind': 'cell'}]},
- {'slug': 'sets',
- 'title': 'Sets',
+ 'Use direct indexing when a missing key should be treated as an error.',
+ 'Snapshot keys with `list(d.keys())` before deleting items in a loop; mutating during '
+ 'iteration raises `RuntimeError`.'],
'section': 'Collections',
- 'summary': 'Sets store unique values and make membership checks explicit.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/datastructures.html#sets',
+ 'see_also': [],
+ 'slug': 'dicts',
+ 'summary': 'Dictionaries map keys to values for records, lookup, and structured data.',
+ 'title': 'Dictionaries',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'profile = {"name": "Ada", "language": "Python"}\n'
+ 'profile["year"] = 1843\n'
+ 'print(profile["name"])\n'
+ 'print(profile.get("timezone", "UTC"))',
+ 'prose': 'Use a dictionary as a small record when fields have names. Direct '
+ 'indexing communicates that the key is required, while `get()` '
+ 'communicates that a missing key has a fallback.'},
+ {'code': 'scores = {"Ada": 10, "Grace": 9}\n'
+ 'print(scores["Grace"])\n'
+ 'print(scores.get("Guido", 0))',
+ 'prose': 'Use a dictionary as a lookup table when keys identify values. This is '
+ 'different from a list, where numeric position is the lookup key.'},
+ {'code': 'for name, score in scores.items():\n print(f"{name}: {score}")',
+ 'prose': 'Use `items()` when the loop needs both keys and values. It avoids '
+ 'looping over keys and then indexing back into the dictionary.'},
+ {'code': 'inventory = {"apple": 0, "pear": 3, "plum": 0}\n'
+ 'for name in list(inventory.keys()):\n'
+ ' if inventory[name] == 0:\n'
+ ' del inventory[name]\n'
+ 'print(inventory)',
+ 'prose': 'Mutating a dictionary while iterating it raises `RuntimeError`. '
+ 'Snapshot the keys with `list(d.keys())` (or build a list of changes '
+ 'and apply them after the loop) so the iteration sees a stable '
+ 'view.'}]},
+ {'cells': [{'code': 'languages = ["python", "go", "python"]\n'
+ 'unique_languages = set(languages)\n'
+ 'print(sorted(unique_languages))',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': "['go', 'python']",
+ 'prose': ['Creating a set removes duplicates. Keep a list when order and repeated '
+ 'values matter; convert to a set when uniqueness is the point.']},
+ {'code': 'allowed = {"python", "rust"}\n'
+ 'print("python" in allowed)\n'
+ 'print("ruby" in allowed)',
+ 'kind': 'cell',
+ 'line': 31,
+ 'output': 'True\nFalse',
+ 'prose': ['Membership checks are the everyday set operation. A list can also use '
+ '`in`, but a set says that membership is central to the data shape.']},
+ {'code': 'compiled = {"go", "rust"}\n'
+ 'print(sorted(allowed | compiled))\n'
+ 'print(sorted(allowed & compiled))\n'
+ 'print(sorted(allowed - compiled))',
+ 'kind': 'cell',
+ 'line': 46,
+ 'output': "['go', 'python', 'rust']\n['rust']\n['python']",
+ 'prose': ['Union, intersection, and difference describe relationships between groups '
+ 'without manual loops.']}],
'code': 'languages = ["python", "go", "python"]\n'
'unique_languages = set(languages)\n'
'print(sorted(unique_languages))\n'
@@ -1393,39 +2888,76 @@
'print(sorted(allowed | compiled))\n'
'print(sorted(allowed & compiled))\n'
'print(sorted(allowed - compiled))\n',
- 'expected_output': "['go', 'python']\nTrue\nFalse\n['go', 'python', 'rust']\n['rust']\n['python']\n",
+ 'doc_path': '/tutorial/datastructures.html#sets',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/datastructures.html#sets',
+ 'expected_output': "['go', 'python']\n"
+ 'True\n'
+ 'False\n'
+ "['go', 'python', 'rust']\n"
+ "['rust']\n"
+ "['python']\n",
+ 'explanation': ['Sets store unique hashable values. Use them when membership and de-duplication '
+ 'matter more than order.',
+ 'A list can answer membership with `in`, but a set communicates that membership '
+ 'is the main operation. Set algebra then expresses how groups relate to each '
+ 'other.',
+ 'Because sets are unordered, examples often wrap output in `sorted()` so the '
+ 'display is deterministic.'],
+ 'min_python': None,
'notes': ['Use lists when order and repeated values matter.',
'Use sets when uniqueness and membership are the main operations.',
'Prefer lists when order or repeated values are part of the meaning.',
'Sets are unordered, so sort them when examples need deterministic display.'],
- 'cells': [{'prose': ['Creating a set removes duplicates. Keep a list when order and repeated values matter; convert '
- 'to a set when uniqueness is the point.'],
- 'code': 'languages = ["python", "go", "python"]\n'
- 'unique_languages = set(languages)\n'
- 'print(sorted(unique_languages))',
- 'output': "['go', 'python']",
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['Membership checks are the everyday set operation. A list can also use `in`, but a set says '
- 'that membership is central to the data shape.'],
- 'code': 'allowed = {"python", "rust"}\nprint("python" in allowed)\nprint("ruby" in allowed)',
- 'output': 'True\nFalse',
- 'line': 31,
- 'kind': 'cell'},
- {'prose': ['Union, intersection, and difference describe relationships between groups without manual '
- 'loops.'],
- 'code': 'compiled = {"go", "rust"}\n'
- 'print(sorted(allowed | compiled))\n'
- 'print(sorted(allowed & compiled))\n'
- 'print(sorted(allowed - compiled))',
- 'output': "['go', 'python', 'rust']\n['rust']\n['python']",
- 'line': 46,
- 'kind': 'cell'}]},
- {'slug': 'slices',
- 'title': 'Slices',
'section': 'Collections',
- 'summary': 'Slices copy meaningful ranges from ordered sequences.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/introduction.html#lists',
+ 'see_also': [],
+ 'slug': 'sets',
+ 'summary': 'Sets store unique values and make membership checks explicit.',
+ 'title': 'Sets',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'languages = ["python", "go", "python"]\n'
+ 'unique_languages = set(languages)\n'
+ 'print(sorted(unique_languages))',
+ 'prose': 'Creating a set removes duplicates. Keep a list when order and '
+ 'repeated values matter; convert to a set when uniqueness is the '
+ 'point.'},
+ {'code': 'allowed = {"python", "rust"}\n'
+ 'print("python" in allowed)\n'
+ 'print("ruby" in allowed)',
+ 'prose': 'Membership checks are the everyday set operation. A list can also use '
+ '`in`, but a set says that membership is central to the data shape.'},
+ {'code': 'compiled = {"go", "rust"}\n'
+ 'print(sorted(allowed | compiled))\n'
+ 'print(sorted(allowed & compiled))\n'
+ 'print(sorted(allowed - compiled))',
+ 'prose': 'Union, intersection, and difference describe relationships between '
+ 'groups without manual loops.'}]},
+ {'cells': [{'code': 'letters = ["a", "b", "c", "d", "e", "f"]\n'
+ 'first_page = letters[:3]\n'
+ 'rest = letters[3:]\n'
+ 'print(first_page)\n'
+ 'print(rest)',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': "['a', 'b', 'c']\n['d', 'e', 'f']",
+ 'prose': ['Omitted bounds mean “from the beginning” or “through the end.” Because the '
+ 'stop index is excluded, adjacent slices split a sequence cleanly.']},
+ {'code': 'middle = letters[1:5]\n'
+ 'every_other = letters[::2]\n'
+ 'reversed_letters = letters[::-1]\n'
+ 'print(middle)\n'
+ 'print(every_other)\n'
+ 'print(reversed_letters)\n'
+ 'print(letters)',
+ 'kind': 'cell',
+ 'line': 34,
+ 'output': "['b', 'c', 'd', 'e']\n"
+ "['a', 'c', 'e']\n"
+ "['f', 'e', 'd', 'c', 'b', 'a']\n"
+ "['a', 'b', 'c', 'd', 'e', 'f']",
+ 'prose': ['Use `start:stop` for a middle range and `step` when you want to skip or '
+ 'walk backward. These operations return new lists; the original list is '
+ 'unchanged.']}],
'code': 'letters = ["a", "b", "c", "d", "e", "f"]\n'
'first_page = letters[:3]\n'
'rest = letters[3:]\n'
@@ -1439,45 +2971,74 @@
'print(every_other)\n'
'print(reversed_letters)\n'
'print(letters)\n',
+ 'doc_path': '/tutorial/introduction.html#lists',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/introduction.html#lists',
'expected_output': "['a', 'b', 'c']\n"
"['d', 'e', 'f']\n"
"['b', 'c', 'd', 'e']\n"
"['a', 'c', 'e']\n"
"['f', 'e', 'd', 'c', 'b', 'a']\n"
"['a', 'b', 'c', 'd', 'e', 'f']\n",
+ 'explanation': ['Slicing reads a range from an ordered sequence with `start:stop:step`. It '
+ 'exists because Python code often needs a meaningful piece of a sequence: a '
+ 'page, a prefix, a tail, a stride, or a reversed view.',
+ 'The stop index is excluded. That convention makes lengths and adjacent ranges '
+ 'line up: `items[:3]` and `items[3:]` split a sequence without overlap.',
+ 'Slices return new sequence objects for built-in lists and strings. Use indexing '
+ 'for one item; use slicing when the result should still be a sequence.'],
+ 'min_python': None,
'notes': ['Slice stop indexes are excluded, so adjacent ranges compose cleanly.',
'Omitted bounds mean the beginning or end of the sequence.',
'A negative step walks backward; `[::-1]` is a common reversed-copy idiom.'],
- 'cells': [{'prose': ['Omitted bounds mean “from the beginning” or “through the end.” Because the stop index is '
- 'excluded, adjacent slices split a sequence cleanly.'],
- 'code': 'letters = ["a", "b", "c", "d", "e", "f"]\n'
- 'first_page = letters[:3]\n'
- 'rest = letters[3:]\n'
- 'print(first_page)\n'
- 'print(rest)',
- 'output': "['a', 'b', 'c']\n['d', 'e', 'f']",
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['Use `start:stop` for a middle range and `step` when you want to skip or walk backward. These '
- 'operations return new lists; the original list is unchanged.'],
- 'code': 'middle = letters[1:5]\n'
- 'every_other = letters[::2]\n'
- 'reversed_letters = letters[::-1]\n'
- 'print(middle)\n'
- 'print(every_other)\n'
- 'print(reversed_letters)\n'
- 'print(letters)',
- 'output': "['b', 'c', 'd', 'e']\n"
- "['a', 'c', 'e']\n"
- "['f', 'e', 'd', 'c', 'b', 'a']\n"
- "['a', 'b', 'c', 'd', 'e', 'f']",
- 'line': 34,
- 'kind': 'cell'}]},
- {'slug': 'comprehensions',
- 'title': 'Comprehensions',
'section': 'Collections',
- 'summary': 'Comprehensions build collections by mapping and filtering iterables.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/datastructures.html#list-comprehensions',
+ 'see_also': [],
+ 'slug': 'slices',
+ 'summary': 'Slices copy meaningful ranges from ordered sequences.',
+ 'title': 'Slices',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'letters = ["a", "b", "c", "d", "e", "f"]\n'
+ 'first_page = letters[:3]\n'
+ 'rest = letters[3:]\n'
+ 'print(first_page)\n'
+ 'print(rest)',
+ 'prose': 'Omitted bounds mean “from the beginning” or “through the end.” '
+ 'Because the stop index is excluded, adjacent slices split a sequence '
+ 'cleanly.'},
+ {'code': 'middle = letters[1:5]\n'
+ 'every_other = letters[::2]\n'
+ 'reversed_letters = letters[::-1]\n'
+ 'print(middle)\n'
+ 'print(every_other)\n'
+ 'print(reversed_letters)\n'
+ 'print(letters)',
+ 'prose': 'Use `start:stop` for a middle range and `step` when you want to skip '
+ 'or walk backward. These operations return new lists; the original '
+ 'list is unchanged.'}]},
+ {'cells': [{'code': 'names = ["ada", "guido", "grace"]\n'
+ 'titled = [name.title() for name in names]\n'
+ 'print(titled)',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': "['Ada', 'Guido', 'Grace']",
+ 'prose': ['A list comprehension maps each input item to one output item. This one '
+ 'calls `title()` for every name and collects the results in a new list.']},
+ {'code': 'scores = {"Ada": 10, "Guido": 8, "Grace": 10}\n'
+ 'high_scores = {name: score for name, score in scores.items() if score >= '
+ '10}\n'
+ 'print(high_scores)',
+ 'kind': 'cell',
+ 'line': 31,
+ 'output': "{'Ada': 10, 'Grace': 10}",
+ 'prose': ['Add an `if` clause when only some items should appear. A dictionary '
+ 'comprehension can transform key/value pairs while preserving the '
+ 'dictionary shape.']},
+ {'code': 'unique_scores = {score for score in scores.values()}\nprint(unique_scores)',
+ 'kind': 'cell',
+ 'line': 45,
+ 'output': '{8, 10}',
+ 'prose': ['A set comprehension keeps only unique results. Here two people have the '
+ 'same score, so the resulting set has two values.']}],
'code': 'names = ["ada", "guido", "grace"]\n'
'titled = [name.title() for name in names]\n'
'print(titled)\n'
@@ -1488,36 +3049,65 @@
'\n'
'unique_scores = {score for score in scores.values()}\n'
'print(unique_scores)\n',
+ 'doc_path': '/tutorial/datastructures.html#list-comprehensions',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/datastructures.html#list-comprehensions',
'expected_output': "['Ada', 'Guido', 'Grace']\n{'Ada': 10, 'Grace': 10}\n{8, 10}\n",
+ 'explanation': ['Comprehensions are expression forms for building concrete collections from '
+ 'iterables. Read them from left to right: produce this value, for each item, '
+ 'optionally only when a condition is true.',
+ 'They are best for direct transformations where the expression is still easy to '
+ 'scan. When the work needs several statements or names, an explicit loop is '
+ 'usually clearer.',
+ 'List, dictionary, and set comprehensions are eager: they build collections '
+ 'immediately. Generator expressions use similar syntax to stream values later '
+ 'and are covered in the Iteration section.'],
+ 'min_python': None,
'notes': ['The left side says what to produce; the `for` clause says where values come from.',
'Use an `if` clause for simple filters.',
'List, dict, and set comprehensions build concrete collections immediately.',
'Switch to a loop when the transformation needs multiple steps or explanations.'],
- 'cells': [{'prose': ['A list comprehension maps each input item to one output item. This one calls `title()` for '
- 'every name and collects the results in a new list.'],
- 'code': 'names = ["ada", "guido", "grace"]\ntitled = [name.title() for name in names]\nprint(titled)',
- 'output': "['Ada', 'Guido', 'Grace']",
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['Add an `if` clause when only some items should appear. A dictionary comprehension can '
- 'transform key/value pairs while preserving the dictionary shape.'],
- 'code': 'scores = {"Ada": 10, "Guido": 8, "Grace": 10}\n'
- 'high_scores = {name: score for name, score in scores.items() if score >= 10}\n'
- 'print(high_scores)',
- 'output': "{'Ada': 10, 'Grace': 10}",
- 'line': 31,
- 'kind': 'cell'},
- {'prose': ['A set comprehension keeps only unique results. Here two people have the same score, so the '
- 'resulting set has two values.'],
- 'code': 'unique_scores = {score for score in scores.values()}\nprint(unique_scores)',
- 'output': '{8, 10}',
- 'line': 45,
- 'kind': 'cell'}]},
- {'slug': 'comprehension-patterns',
- 'title': 'Comprehension Patterns',
'section': 'Collections',
- 'summary': 'Comprehensions can use multiple for clauses and filters when the shape stays clear.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/datastructures.html#list-comprehensions',
+ 'see_also': [],
+ 'slug': 'comprehensions',
+ 'summary': 'Comprehensions build collections by mapping and filtering iterables.',
+ 'title': 'Comprehensions',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'names = ["ada", "guido", "grace"]\n'
+ 'titled = [name.title() for name in names]\n'
+ 'print(titled)',
+ 'prose': 'A list comprehension maps each input item to one output item. This '
+ 'one calls `title()` for every name and collects the results in a new '
+ 'list.'},
+ {'code': 'scores = {"Ada": 10, "Guido": 8, "Grace": 10}\n'
+ 'high_scores = {name: score for name, score in scores.items() if score '
+ '>= 10}\n'
+ 'print(high_scores)',
+ 'prose': 'Add an `if` clause when only some items should appear. A dictionary '
+ 'comprehension can transform key/value pairs while preserving the '
+ 'dictionary shape.'},
+ {'code': 'unique_scores = {score for score in scores.values()}\n'
+ 'print(unique_scores)',
+ 'prose': 'A set comprehension keeps only unique results. Here two people have '
+ 'the same score, so the resulting set has two values.'}]},
+ {'cells': [{'code': 'colors = ["red", "blue"]\n'
+ 'sizes = ["S", "M"]\n'
+ 'variants = [(color, size) for color in colors for size in sizes]\n'
+ 'print(variants)',
+ 'kind': 'cell',
+ 'line': 22,
+ 'output': "[('red', 'S'), ('red', 'M'), ('blue', 'S'), ('blue', 'M')]",
+ 'prose': ['Multiple `for` clauses behave like nested loops. The leftmost `for` is the '
+ 'outer loop, and the next `for` runs inside it.']},
+ {'code': 'numbers = range(10)\n'
+ 'filtered = [n for n in numbers if n % 2 == 0 if n > 2]\n'
+ 'print(filtered)',
+ 'kind': 'cell',
+ 'line': 37,
+ 'output': '[4, 6, 8]',
+ 'prose': ['Multiple `if` clauses filter values. They are useful for simple '
+ 'conditions, but an explicit loop is clearer when the rules need names or '
+ 'explanation.']}],
'code': 'colors = ["red", "blue"]\n'
'sizes = ["S", "M"]\n'
'variants = [(color, size) for color in colors for size in sizes]\n'
@@ -1526,30 +3116,64 @@
'numbers = range(10)\n'
'filtered = [n for n in numbers if n % 2 == 0 if n > 2]\n'
'print(filtered)\n',
+ 'doc_path': '/tutorial/datastructures.html#list-comprehensions',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/datastructures.html#list-comprehensions',
'expected_output': "[('red', 'S'), ('red', 'M'), ('blue', 'S'), ('blue', 'M')]\n[4, 6, 8]\n",
+ 'explanation': ['Comprehensions can contain more than one `for` clause and more than one `if` '
+ 'filter. The clauses are read in the same order as nested loops.',
+ 'Use these forms only while the shape remains easy to scan. If a comprehension '
+ 'starts needing several names, comments, or branches, an explicit loop is '
+ 'usually better.',
+ 'Nested comprehensions build concrete collections immediately, just like simpler '
+ 'list, dict, and set comprehensions.'],
+ 'min_python': None,
'notes': ['Read comprehension clauses from left to right.',
'Multiple `for` clauses act like nested loops.',
'Prefer an explicit loop when the comprehension stops being obvious.'],
- 'cells': [{'prose': ['Multiple `for` clauses behave like nested loops. The leftmost `for` is the outer loop, and the '
- 'next `for` runs inside it.'],
- 'code': 'colors = ["red", "blue"]\n'
- 'sizes = ["S", "M"]\n'
- 'variants = [(color, size) for color in colors for size in sizes]\n'
- 'print(variants)',
- 'output': "[('red', 'S'), ('red', 'M'), ('blue', 'S'), ('blue', 'M')]",
- 'line': 22,
- 'kind': 'cell'},
- {'prose': ['Multiple `if` clauses filter values. They are useful for simple conditions, but an explicit '
- 'loop is clearer when the rules need names or explanation.'],
- 'code': 'numbers = range(10)\nfiltered = [n for n in numbers if n % 2 == 0 if n > 2]\nprint(filtered)',
- 'output': '[4, 6, 8]',
- 'line': 37,
- 'kind': 'cell'}]},
- {'slug': 'sorting',
- 'title': 'Sorting',
'section': 'Collections',
- 'summary': 'sorted returns a new ordered list and key functions choose the sort value.',
- 'doc_url': 'https://docs.python.org/3.13/howto/sorting.html',
+ 'see_also': ['comprehensions', 'generator-expressions', 'for-loops'],
+ 'slug': 'comprehension-patterns',
+ 'summary': 'Comprehensions can use multiple for clauses and filters when the shape stays clear.',
+ 'title': 'Comprehension Patterns',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'colors = ["red", "blue"]\n'
+ 'sizes = ["S", "M"]\n'
+ 'variants = [(color, size) for color in colors for size in sizes]\n'
+ 'print(variants)',
+ 'prose': 'Multiple `for` clauses behave like nested loops. The leftmost `for` '
+ 'is the outer loop, and the next `for` runs inside it.'},
+ {'code': 'numbers = range(10)\n'
+ 'filtered = [n for n in numbers if n % 2 == 0 if n > 2]\n'
+ 'print(filtered)',
+ 'prose': 'Multiple `if` clauses filter values. They are useful for simple '
+ 'conditions, but an explicit loop is clearer when the rules need names '
+ 'or explanation.'}]},
+ {'cells': [{'code': 'names = ["Guido", "Ada", "Grace"]\nprint(sorted(names))\nprint(names)',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': "['Ada', 'Grace', 'Guido']\n['Guido', 'Ada', 'Grace']",
+ 'prose': ['`sorted()` returns a new list. Printing the original list afterward shows '
+ 'that the input order did not change.']},
+ {'code': 'users = [\n'
+ ' {"name": "Ada", "score": 10},\n'
+ ' {"name": "Guido", "score": 8},\n'
+ ' {"name": "Grace", "score": 10},\n'
+ ']\n'
+ 'ranked = sorted(users, key=lambda user: user["score"], reverse=True)\n'
+ 'print([user["name"] for user in ranked])',
+ 'kind': 'cell',
+ 'line': 32,
+ 'output': "['Ada', 'Grace', 'Guido']",
+ 'prose': ['A key function computes the value to compare. Here the records are sorted '
+ 'by score, highest first, and the output shows the resulting order.']},
+ {'code': 'users.sort(key=lambda user: user["name"])\n'
+ 'print([user["name"] for user in users])',
+ 'kind': 'cell',
+ 'line': 50,
+ 'output': "['Ada', 'Grace', 'Guido']",
+ 'prose': ['`list.sort()` sorts the list in place. Use it when mutation is the point '
+ 'and no separate sorted copy is needed.']}],
'code': 'names = ["Guido", "Ada", "Grace"]\n'
'print(sorted(names))\n'
'print(names)\n'
@@ -1564,64 +3188,47 @@
'\n'
'users.sort(key=lambda user: user["name"])\n'
'print([user["name"] for user in users])\n',
+ 'doc_path': '/howto/sorting.html',
+ 'doc_url': 'https://docs.python.org/3.13/howto/sorting.html',
'expected_output': "['Ada', 'Grace', 'Guido']\n"
"['Guido', 'Ada', 'Grace']\n"
"['Ada', 'Grace', 'Guido']\n"
"['Ada', 'Grace', 'Guido']\n",
+ 'explanation': ['`sorted()` accepts any iterable and returns a new list. The original collection '
+ 'is left untouched, which makes `sorted()` useful in expressions and pipelines.',
+ 'Use `key=` to say what value should be compared for each item. This is the '
+ 'idiomatic way to sort records, tuples, dictionaries, and objects by a field.',
+ 'Use `reverse=True` for descending order. Use `list.sort()` instead when you '
+ 'intentionally want to mutate an existing list in place.'],
+ 'min_python': None,
'notes': ['`sorted()` makes a new list; `list.sort()` mutates an existing list.',
'`key=` should return the value Python compares for each item.',
"Python's sort is stable, so equal keys keep their original relative order."],
- 'cells': [{'prose': ['`sorted()` returns a new list. Printing the original list afterward shows that the input order '
- 'did not change.'],
- 'code': 'names = ["Guido", "Ada", "Grace"]\nprint(sorted(names))\nprint(names)',
- 'output': "['Ada', 'Grace', 'Guido']\n['Guido', 'Ada', 'Grace']",
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['A key function computes the value to compare. Here the records are sorted by score, highest '
- 'first, and the output shows the resulting order.'],
- 'code': 'users = [\n'
- ' {"name": "Ada", "score": 10},\n'
- ' {"name": "Guido", "score": 8},\n'
- ' {"name": "Grace", "score": 10},\n'
- ']\n'
- 'ranked = sorted(users, key=lambda user: user["score"], reverse=True)\n'
- 'print([user["name"] for user in ranked])',
- 'output': "['Ada', 'Grace', 'Guido']",
- 'line': 32,
- 'kind': 'cell'},
- {'prose': ['`list.sort()` sorts the list in place. Use it when mutation is the point and no separate '
- 'sorted copy is needed.'],
- 'code': 'users.sort(key=lambda user: user["name"])\nprint([user["name"] for user in users])',
- 'output': "['Ada', 'Grace', 'Guido']",
- 'line': 50,
- 'kind': 'cell'}]},
- {'slug': 'collections-module',
- 'title': 'Collections Module',
'section': 'Collections',
- 'summary': 'collections provides specialized containers for common data shapes.',
- 'doc_url': 'https://docs.python.org/3.13/library/collections.html',
- 'code': 'from collections import Counter, defaultdict, deque, namedtuple\n'
- '\n'
- 'counts = Counter("banana")\n'
- 'print(counts.most_common(2))\n'
- '\n'
- 'groups = defaultdict(list)\n'
- 'for name, team in [("Ada", "red"), ("Grace", "blue"), ("Lin", "red")]:\n'
- ' groups[team].append(name)\n'
- 'print(dict(groups))\n'
- '\n'
- 'queue = deque(["first"])\n'
- 'queue.append("second")\n'
- 'print(queue.popleft())\n'
- '\n'
- 'Point = namedtuple("Point", "x y")\n'
- 'print(Point(2, 3).x)\n',
- 'expected_output': "[('a', 3), ('n', 2)]\n{'red': ['Ada', 'Lin'], 'blue': ['Grace']}\nfirst\n2\n",
- 'notes': ['Use `Counter` when counting is the data shape.',
- 'Use `defaultdict` when grouping values by key.',
- 'Use `deque` for efficient queue operations and `namedtuple` for lightweight named records.'],
- 'cells': [{'prose': ['Use `Counter` when counting is the data shape.'],
- 'code': 'from collections import Counter, defaultdict, deque, namedtuple\n'
+ 'see_also': [],
+ 'slug': 'sorting',
+ 'summary': 'sorted returns a new ordered list and key functions choose the sort value.',
+ 'title': 'Sorting',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'names = ["Guido", "Ada", "Grace"]\nprint(sorted(names))\nprint(names)',
+ 'prose': '`sorted()` returns a new list. Printing the original list afterward '
+ 'shows that the input order did not change.'},
+ {'code': 'users = [\n'
+ ' {"name": "Ada", "score": 10},\n'
+ ' {"name": "Guido", "score": 8},\n'
+ ' {"name": "Grace", "score": 10},\n'
+ ']\n'
+ 'ranked = sorted(users, key=lambda user: user["score"], reverse=True)\n'
+ 'print([user["name"] for user in ranked])',
+ 'prose': 'A key function computes the value to compare. Here the records are '
+ 'sorted by score, highest first, and the output shows the resulting '
+ 'order.'},
+ {'code': 'users.sort(key=lambda user: user["name"])\n'
+ 'print([user["name"] for user in users])',
+ 'prose': '`list.sort()` sorts the list in place. Use it when mutation is the '
+ 'point and no separate sorted copy is needed.'}]},
+ {'cells': [{'code': 'from collections import Counter, defaultdict, deque, namedtuple\n'
'\n'
'counts = Counter("banana")\n'
'print(counts.most_common(2))\n'
@@ -1637,14 +3244,84 @@
'\n'
'Point = namedtuple("Point", "x y")\n'
'print(Point(2, 3).x)',
- 'output': "[('a', 3), ('n', 2)]\n{'red': ['Ada', 'Lin'], 'blue': ['Grace']}\nfirst\n2",
+ 'kind': 'cell',
'line': 17,
- 'kind': 'cell'}]},
- {'slug': 'copying-collections',
- 'title': 'Copying Collections',
+ 'output': "[('a', 3), ('n', 2)]\n{'red': ['Ada', 'Lin'], 'blue': ['Grace']}\nfirst\n2",
+ 'prose': ['Use `Counter` when counting is the data shape.']}],
+ 'code': 'from collections import Counter, defaultdict, deque, namedtuple\n'
+ '\n'
+ 'counts = Counter("banana")\n'
+ 'print(counts.most_common(2))\n'
+ '\n'
+ 'groups = defaultdict(list)\n'
+ 'for name, team in [("Ada", "red"), ("Grace", "blue"), ("Lin", "red")]:\n'
+ ' groups[team].append(name)\n'
+ 'print(dict(groups))\n'
+ '\n'
+ 'queue = deque(["first"])\n'
+ 'queue.append("second")\n'
+ 'print(queue.popleft())\n'
+ '\n'
+ 'Point = namedtuple("Point", "x y")\n'
+ 'print(Point(2, 3).x)\n',
+ 'doc_path': '/library/collections.html',
+ 'doc_url': 'https://docs.python.org/3.13/library/collections.html',
+ 'expected_output': "[('a', 3), ('n', 2)]\n{'red': ['Ada', 'Lin'], 'blue': ['Grace']}\nfirst\n2\n",
+ 'explanation': ['collections provides specialized containers for common data shapes. It exists '
+ 'to make a common boundary explicit instead of leaving the behavior implicit in '
+ 'a larger program.',
+ 'Use it when the problem shape matches the example, and prefer simpler '
+ 'neighboring tools when the extra machinery would hide the intent. The notes '
+ 'call out the boundary so the feature stays practical rather than decorative.',
+ 'The example is small, deterministic, and focused on the semantic point. The '
+ 'complete source is editable below, while the walkthrough pairs the source with '
+ 'its output.'],
+ 'min_python': None,
+ 'notes': ['Use `Counter` when counting is the data shape.',
+ 'Use `defaultdict` when grouping values by key.',
+ 'Use `deque` for efficient queue operations and `namedtuple` for lightweight named '
+ 'records.'],
'section': 'Collections',
- 'summary': 'Copies can duplicate a container while still sharing nested objects.',
- 'doc_url': 'https://docs.python.org/3.13/library/copy.html',
+ 'see_also': [],
+ 'slug': 'collections-module',
+ 'summary': 'collections provides specialized containers for common data shapes.',
+ 'title': 'Collections Module',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'from collections import Counter, defaultdict, deque, namedtuple\n'
+ '\n'
+ 'counts = Counter("banana")\n'
+ 'print(counts.most_common(2))\n'
+ '\n'
+ 'groups = defaultdict(list)\n'
+ 'for name, team in [("Ada", "red"), ("Grace", "blue"), ("Lin", '
+ '"red")]:\n'
+ ' groups[team].append(name)\n'
+ 'print(dict(groups))\n'
+ '\n'
+ 'queue = deque(["first"])\n'
+ 'queue.append("second")\n'
+ 'print(queue.popleft())\n'
+ '\n'
+ 'Point = namedtuple("Point", "x y")\n'
+ 'print(Point(2, 3).x)',
+ 'prose': 'Use `Counter` when counting is the data shape.'}]},
+ {'cells': [{'code': 'import copy\n'
+ '\n'
+ 'rows = [["Ada"], ["Grace"]]\n'
+ 'shallow = rows.copy()\n'
+ 'deep = copy.deepcopy(rows)\n'
+ '\n'
+ 'rows[0].append("Lovelace")\n'
+ '\n'
+ 'print(shallow)\n'
+ 'print(deep)\n'
+ 'print(rows[0] is shallow[0])\n'
+ 'print(rows[0] is deep[0])',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': "[['Ada', 'Lovelace'], ['Grace']]\n[['Ada'], ['Grace']]\nTrue\nFalse",
+ 'prose': ['A shallow copy makes a new outer container.']}],
'code': 'import copy\n'
'\n'
'rows = [["Ada"], ["Grace"]]\n'
@@ -1657,31 +3334,92 @@
'print(deep)\n'
'print(rows[0] is shallow[0])\n'
'print(rows[0] is deep[0])\n',
+ 'doc_path': '/library/copy.html',
+ 'doc_url': 'https://docs.python.org/3.13/library/copy.html',
'expected_output': "[['Ada', 'Lovelace'], ['Grace']]\n[['Ada'], ['Grace']]\nTrue\nFalse\n",
+ 'explanation': ['Copies can duplicate a container while still sharing nested objects. It exists '
+ 'to make a common boundary explicit instead of leaving the behavior implicit in '
+ 'a larger program.',
+ 'Use it when the problem shape matches the example, and prefer simpler '
+ 'neighboring tools when the extra machinery would hide the intent. The notes '
+ 'call out the boundary so the feature stays practical rather than decorative.',
+ 'The example is small, deterministic, and focused on the semantic point. The '
+ 'complete source is editable below, while the walkthrough pairs the source with '
+ 'its output.'],
+ 'min_python': None,
'notes': ['A shallow copy makes a new outer container.',
'Nested objects are still shared by a shallow copy.',
'Use `copy.deepcopy()` only when nested independence is required.'],
- 'cells': [{'prose': ['A shallow copy makes a new outer container.'],
- 'code': 'import copy\n'
- '\n'
- 'rows = [["Ada"], ["Grace"]]\n'
- 'shallow = rows.copy()\n'
- 'deep = copy.deepcopy(rows)\n'
+ 'section': 'Collections',
+ 'see_also': [],
+ 'slug': 'copying-collections',
+ 'summary': 'Copies can duplicate a container while still sharing nested objects.',
+ 'title': 'Copying Collections',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'import copy\n'
+ '\n'
+ 'rows = [["Ada"], ["Grace"]]\n'
+ 'shallow = rows.copy()\n'
+ 'deep = copy.deepcopy(rows)\n'
+ '\n'
+ 'rows[0].append("Lovelace")\n'
+ '\n'
+ 'print(shallow)\n'
+ 'print(deep)\n'
+ 'print(rows[0] is shallow[0])\n'
+ 'print(rows[0] is deep[0])',
+ 'prose': 'A shallow copy makes a new outer container.'}]},
+ {'cells': [{'code': 'def greet(name):\n return f"Hello, {name}."\n\nprint(greet("Python"))',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': 'Hello, Python.',
+ 'prose': ['`return` sends a value back to the caller. The caller can print it, store '
+ 'it, or pass it to another function.']},
+ {'code': 'def format_total(amount, currency="USD"):\n'
+ ' return f"{amount} {currency}"\n'
'\n'
- 'rows[0].append("Lovelace")\n'
+ 'print(format_total(10))\n'
+ 'print(format_total(10, currency="EUR"))',
+ 'kind': 'cell',
+ 'line': 32,
+ 'output': '10 USD\n10 EUR',
+ 'prose': ['Default arguments provide common values. Keyword arguments make it clear '
+ 'which option is being overridden.']},
+ {'code': 'def log(message):\n'
+ ' print(f"log: {message}")\n'
'\n'
- 'print(shallow)\n'
- 'print(deep)\n'
- 'print(rows[0] is shallow[0])\n'
- 'print(rows[0] is deep[0])',
- 'output': "[['Ada', 'Lovelace'], ['Grace']]\n[['Ada'], ['Grace']]\nTrue\nFalse",
- 'line': 17,
- 'kind': 'cell'}]},
- {'slug': 'functions',
- 'title': 'Functions',
- 'section': 'Functions',
- 'summary': 'Use def to name reusable behavior and return results.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#defining-functions',
+ 'result = log("saved")\n'
+ 'print(result)',
+ 'kind': 'cell',
+ 'line': 49,
+ 'output': 'log: saved\nNone',
+ 'prose': ['A function without an explicit `return` returns `None`. That makes '
+ 'side-effect-only functions easy to distinguish from value-producing '
+ 'ones.']},
+ {'code': 'def append_broken(item, items=[]):\n'
+ ' items.append(item)\n'
+ ' return items\n'
+ '\n'
+ 'print(append_broken("a"))\n'
+ 'print(append_broken("b"))\n'
+ '\n'
+ '\n'
+ 'def append_fixed(item, items=None):\n'
+ ' if items is None:\n'
+ ' items = []\n'
+ ' items.append(item)\n'
+ ' return items\n'
+ '\n'
+ 'print(append_fixed("a"))\n'
+ 'print(append_fixed("b"))',
+ 'kind': 'cell',
+ 'line': 66,
+ 'output': "['a']\n['a', 'b']\n['a']\n['b']",
+ 'prose': ['Mutable default arguments are evaluated once when the function is defined, '
+ 'not on each call. The same list is shared across calls, so successive '
+ "calls see each other's mutations. Use `None` as the sentinel and create a "
+ 'fresh container inside the body.']}],
'code': 'def greet(name):\n'
' return f"Hello, {name}."\n'
'\n'
@@ -1699,38 +3437,119 @@
' print(f"log: {message}")\n'
'\n'
'result = log("saved")\n'
- 'print(result)\n',
- 'expected_output': 'Hello, Python.\n10 USD\n10 EUR\nlog: saved\nNone\n',
+ 'print(result)\n'
+ '\n'
+ '\n'
+ 'def append_broken(item, items=[]):\n'
+ ' items.append(item)\n'
+ ' return items\n'
+ '\n'
+ 'print(append_broken("a"))\n'
+ 'print(append_broken("b"))\n'
+ '\n'
+ '\n'
+ 'def append_fixed(item, items=None):\n'
+ ' if items is None:\n'
+ ' items = []\n'
+ ' items.append(item)\n'
+ ' return items\n'
+ '\n'
+ 'print(append_fixed("a"))\n'
+ 'print(append_fixed("b"))\n',
+ 'doc_path': '/tutorial/controlflow.html#defining-functions',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#defining-functions',
+ 'expected_output': 'Hello, Python.\n'
+ '10 USD\n'
+ '10 EUR\n'
+ 'log: saved\n'
+ 'None\n'
+ "['a']\n"
+ "['a', 'b']\n"
+ "['a']\n"
+ "['b']\n",
+ 'explanation': ['Functions package behavior behind a name. `def` creates a function object that '
+ 'can accept arguments, compute values, and return a result.',
+ 'Default arguments make common calls short, and keyword arguments make call '
+ 'sites easier to read. A function that reaches the end without `return` produces '
+ '`None`.',
+ 'Use functions when a calculation has a useful name, when code repeats, or when '
+ 'a piece of behavior should be tested independently.'],
+ 'min_python': None,
'notes': ['Use `return` for values the caller should receive.',
'Defaults keep common calls concise.',
- 'Keyword arguments make options readable at the call site.'],
- 'cells': [{'prose': ['`return` sends a value back to the caller. The caller can print it, store it, or pass it to '
- 'another function.'],
- 'code': 'def greet(name):\n return f"Hello, {name}."\n\nprint(greet("Python"))',
- 'output': 'Hello, Python.',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['Default arguments provide common values. Keyword arguments make it clear which option is being '
- 'overridden.'],
- 'code': 'def format_total(amount, currency="USD"):\n'
- ' return f"{amount} {currency}"\n'
- '\n'
- 'print(format_total(10))\n'
- 'print(format_total(10, currency="EUR"))',
- 'output': '10 USD\n10 EUR',
- 'line': 32,
- 'kind': 'cell'},
- {'prose': ['A function without an explicit `return` returns `None`. That makes side-effect-only functions '
- 'easy to distinguish from value-producing ones.'],
- 'code': 'def log(message):\n print(f"log: {message}")\n\nresult = log("saved")\nprint(result)',
- 'output': 'log: saved\nNone',
- 'line': 49,
- 'kind': 'cell'}]},
- {'slug': 'keyword-only-arguments',
- 'title': 'Keyword-only Arguments',
+ 'Keyword arguments make options readable at the call site.',
+ 'Never use a mutable value as a default argument; use `None` and build the container '
+ 'inside the function body.'],
'section': 'Functions',
- 'summary': 'Use * to require selected function arguments to be named.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#special-parameters',
+ 'see_also': [],
+ 'slug': 'functions',
+ 'summary': 'Use def to name reusable behavior and return results.',
+ 'title': 'Functions',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'def greet(name):\n'
+ ' return f"Hello, {name}."\n'
+ '\n'
+ 'print(greet("Python"))',
+ 'prose': '`return` sends a value back to the caller. The caller can print it, '
+ 'store it, or pass it to another function.'},
+ {'code': 'def format_total(amount, currency="USD"):\n'
+ ' return f"{amount} {currency}"\n'
+ '\n'
+ 'print(format_total(10))\n'
+ 'print(format_total(10, currency="EUR"))',
+ 'prose': 'Default arguments provide common values. Keyword arguments make it '
+ 'clear which option is being overridden.'},
+ {'code': 'def log(message):\n'
+ ' print(f"log: {message}")\n'
+ '\n'
+ 'result = log("saved")\n'
+ 'print(result)',
+ 'prose': 'A function without an explicit `return` returns `None`. That makes '
+ 'side-effect-only functions easy to distinguish from value-producing '
+ 'ones.'},
+ {'code': 'def append_broken(item, items=[]):\n'
+ ' items.append(item)\n'
+ ' return items\n'
+ '\n'
+ 'print(append_broken("a"))\n'
+ 'print(append_broken("b"))\n'
+ '\n'
+ '\n'
+ 'def append_fixed(item, items=None):\n'
+ ' if items is None:\n'
+ ' items = []\n'
+ ' items.append(item)\n'
+ ' return items\n'
+ '\n'
+ 'print(append_fixed("a"))\n'
+ 'print(append_fixed("b"))',
+ 'prose': 'Mutable default arguments are evaluated once when the function is '
+ 'defined, not on each call. The same list is shared across calls, so '
+ "successive calls see each other's mutations. Use `None` as the "
+ 'sentinel and create a fresh container inside the body.'}]},
+ {'cells': [{'code': 'def connect(host, *, timeout=5, secure=True):\n'
+ ' scheme = "https" if secure else "http"\n'
+ ' print(f"{scheme}://{host} timeout={timeout}")\n'
+ '\n'
+ 'connect("example.com")',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': 'https://example.com timeout=5',
+ 'prose': ['Parameters after `*` must be named. The default options still apply when '
+ 'the caller omits them.']},
+ {'code': 'connect("example.com", timeout=10)',
+ 'kind': 'cell',
+ 'line': 33,
+ 'output': 'https://example.com timeout=10',
+ 'prose': ['Naming the option makes the call site explicit. A reader does not have to '
+ 'remember which positional slot controls the timeout.']},
+ {'code': 'connect("localhost", secure=False)',
+ 'kind': 'cell',
+ 'line': 45,
+ 'output': 'http://localhost timeout=5',
+ 'prose': ['Flags are especially good keyword-only arguments because a bare positional '
+ '`False` is hard to interpret.']}],
'code': 'def connect(host, *, timeout=5, secure=True):\n'
' scheme = "https" if secure else "http"\n'
' print(f"{scheme}://{host} timeout={timeout}")\n'
@@ -1738,37 +3557,60 @@
'connect("example.com")\n'
'connect("example.com", timeout=10)\n'
'connect("localhost", secure=False)\n',
- 'expected_output': 'https://example.com timeout=5\nhttps://example.com timeout=10\nhttp://localhost timeout=5\n',
+ 'doc_path': '/tutorial/controlflow.html#special-parameters',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#special-parameters',
+ 'expected_output': 'https://example.com timeout=5\n'
+ 'https://example.com timeout=10\n'
+ 'http://localhost timeout=5\n',
+ 'explanation': ['A bare `*` in a function signature marks the following parameters as '
+ 'keyword-only. Callers must name those arguments explicitly.',
+ 'Keyword-only arguments are useful for options such as timeouts, flags, and '
+ 'modes where positional calls would be ambiguous or easy to misread.',
+ 'They let the required data stay positional while optional controls remain '
+ 'self-documenting at the call site.'],
+ 'min_python': None,
'notes': ['Put `*` before options that callers should name.',
'Keyword-only flags avoid mysterious positional `True` and `False` arguments.',
'Defaults work normally for keyword-only parameters.'],
- 'cells': [{'prose': ['Parameters after `*` must be named. The default options still apply when the caller omits '
- 'them.'],
- 'code': 'def connect(host, *, timeout=5, secure=True):\n'
- ' scheme = "https" if secure else "http"\n'
- ' print(f"{scheme}://{host} timeout={timeout}")\n'
- '\n'
- 'connect("example.com")',
- 'output': 'https://example.com timeout=5',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['Naming the option makes the call site explicit. A reader does not have to remember which '
- 'positional slot controls the timeout.'],
- 'code': 'connect("example.com", timeout=10)',
- 'output': 'https://example.com timeout=10',
- 'line': 33,
- 'kind': 'cell'},
- {'prose': ['Flags are especially good keyword-only arguments because a bare positional `False` is hard to '
- 'interpret.'],
- 'code': 'connect("localhost", secure=False)',
- 'output': 'http://localhost timeout=5',
- 'line': 45,
- 'kind': 'cell'}]},
- {'slug': 'positional-only-parameters',
- 'title': 'Positional-only Parameters',
'section': 'Functions',
- 'summary': 'Use / to mark parameters that callers must pass by position.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#special-parameters',
+ 'see_also': [],
+ 'slug': 'keyword-only-arguments',
+ 'summary': 'Use * to require selected function arguments to be named.',
+ 'title': 'Keyword-only Arguments',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'def connect(host, *, timeout=5, secure=True):\n'
+ ' scheme = "https" if secure else "http"\n'
+ ' print(f"{scheme}://{host} timeout={timeout}")\n'
+ '\n'
+ 'connect("example.com")',
+ 'prose': 'Parameters after `*` must be named. The default options still apply '
+ 'when the caller omits them.'},
+ {'code': 'connect("example.com", timeout=10)',
+ 'prose': 'Naming the option makes the call site explicit. A reader does not '
+ 'have to remember which positional slot controls the timeout.'},
+ {'code': 'connect("localhost", secure=False)',
+ 'prose': 'Flags are especially good keyword-only arguments because a bare '
+ 'positional `False` is hard to interpret.'}]},
+ {'cells': [{'code': 'def scale(value, /, factor=2, *, clamp=False):\n'
+ ' result = value * factor\n'
+ ' if clamp:\n'
+ ' result = min(result, 10)\n'
+ ' return result\n'
+ '\n'
+ 'print(scale(4))\n'
+ 'print(scale(4, factor=3))',
+ 'kind': 'cell',
+ 'line': 22,
+ 'output': '8\n12',
+ 'prose': ['Parameters before `/` are positional-only. `value` is the main input, '
+ 'while `factor` remains an ordinary parameter that can be named.']},
+ {'code': 'print(scale(4, clamp=True))',
+ 'kind': 'cell',
+ 'line': 42,
+ 'output': '8',
+ 'prose': ['Parameters after `*` are keyword-only. That makes options such as `clamp` '
+ 'explicit at the call site.']}],
'code': 'def scale(value, /, factor=2, *, clamp=False):\n'
' result = value * factor\n'
' if clamp:\n'
@@ -1778,34 +3620,65 @@
'print(scale(4))\n'
'print(scale(4, factor=3))\n'
'print(scale(4, clamp=True))\n',
+ 'doc_path': '/tutorial/controlflow.html#special-parameters',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#special-parameters',
'expected_output': '8\n12\n8\n',
+ 'explanation': ['A `/` in a function signature marks the parameters before it as '
+ 'positional-only. Callers must pass those arguments by position, not by keyword.',
+ 'This is useful when parameter names are implementation details or when an API '
+ 'should match built-in functions that accept positional values.',
+ 'Together, `/` and `*` let a signature draw clear boundaries: positional-only '
+ 'inputs, ordinary inputs, and keyword-only options.'],
+ 'min_python': None,
'notes': ['`/` marks parameters before it as positional-only.',
'`*` marks parameters after it as keyword-only.',
'Use these markers when the call shape is part of the API design.'],
- 'cells': [{'prose': ['Parameters before `/` are positional-only. `value` is the main input, while `factor` remains '
- 'an ordinary parameter that can be named.'],
- 'code': 'def scale(value, /, factor=2, *, clamp=False):\n'
- ' result = value * factor\n'
- ' if clamp:\n'
- ' result = min(result, 10)\n'
- ' return result\n'
- '\n'
- 'print(scale(4))\n'
- 'print(scale(4, factor=3))',
- 'output': '8\n12',
- 'line': 22,
- 'kind': 'cell'},
- {'prose': ['Parameters after `*` are keyword-only. That makes options such as `clamp` explicit at the call '
- 'site.'],
- 'code': 'print(scale(4, clamp=True))',
- 'output': '8',
- 'line': 42,
- 'kind': 'cell'}]},
- {'slug': 'args-and-kwargs',
- 'title': 'Args and Kwargs',
'section': 'Functions',
- 'summary': '*args collects extra positional arguments and **kwargs collects named ones.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#arbitrary-argument-lists',
+ 'see_also': ['keyword-only-arguments', 'functions', 'args-and-kwargs'],
+ 'slug': 'positional-only-parameters',
+ 'summary': 'Use / to mark parameters that callers must pass by position.',
+ 'title': 'Positional-only Parameters',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'def scale(value, /, factor=2, *, clamp=False):\n'
+ ' result = value * factor\n'
+ ' if clamp:\n'
+ ' result = min(result, 10)\n'
+ ' return result\n'
+ '\n'
+ 'print(scale(4))\n'
+ 'print(scale(4, factor=3))',
+ 'prose': 'Parameters before `/` are positional-only. `value` is the main input, '
+ 'while `factor` remains an ordinary parameter that can be named.'},
+ {'code': 'print(scale(4, clamp=True))',
+ 'prose': 'Parameters after `*` are keyword-only. That makes options such as '
+ '`clamp` explicit at the call site.'}]},
+ {'cells': [{'code': 'def total(*numbers):\n return sum(numbers)\n\nprint(total(2, 3, 5))',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': '10',
+ 'prose': ['`*args` collects extra positional arguments into a tuple. This fits '
+ 'functions that naturally accept any number of similar values.']},
+ {'code': 'def describe(**metadata):\n'
+ ' print(metadata)\n'
+ '\n'
+ 'describe(owner="Ada", public=True)',
+ 'kind': 'cell',
+ 'line': 32,
+ 'output': "{'owner': 'Ada', 'public': True}",
+ 'prose': ['`**kwargs` collects named arguments into a dictionary. The names become '
+ 'string keys.']},
+ {'code': 'def report(title, *items, **metadata):\n'
+ ' print(title)\n'
+ ' print(items)\n'
+ ' print(metadata)\n'
+ '\n'
+ 'report("scores", 10, 9, owner="Ada")',
+ 'kind': 'cell',
+ 'line': 47,
+ 'output': "scores\n(10, 9)\n{'owner': 'Ada'}",
+ 'prose': ['A function can combine explicit parameters, `*args`, and `**kwargs`. Put '
+ 'the flexible parts last so the fixed shape remains visible.']}],
'code': 'def total(*numbers):\n'
' return sum(numbers)\n'
'\n'
@@ -1824,37 +3697,62 @@
' print(metadata)\n'
'\n'
'report("scores", 10, 9, owner="Ada")\n',
+ 'doc_path': '/tutorial/controlflow.html#arbitrary-argument-lists',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#arbitrary-argument-lists',
'expected_output': "10\n{'owner': 'Ada', 'public': True}\nscores\n(10, 9)\n{'owner': 'Ada'}\n",
+ 'explanation': ['`*args` and `**kwargs` let a function accept flexible positional and keyword '
+ 'arguments. They are the function-definition counterpart to unpacking at a call '
+ 'site.',
+ 'These parameters are useful for wrappers, decorators, logging helpers, and APIs '
+ 'that forward arguments to another function.',
+ 'They should not replace clear signatures. If a function has a stable interface, '
+ 'explicit parameters document expectations better than a bag of arguments.'],
+ 'min_python': None,
'notes': ['Use these tools when a function naturally accepts a flexible shape.',
'Prefer explicit parameters when the accepted arguments are known and fixed.',
'`*args` is a tuple; `**kwargs` is a dictionary.'],
- 'cells': [{'prose': ['`*args` collects extra positional arguments into a tuple. This fits functions that naturally '
- 'accept any number of similar values.'],
- 'code': 'def total(*numbers):\n return sum(numbers)\n\nprint(total(2, 3, 5))',
- 'output': '10',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['`**kwargs` collects named arguments into a dictionary. The names become string keys.'],
- 'code': 'def describe(**metadata):\n print(metadata)\n\ndescribe(owner="Ada", public=True)',
- 'output': "{'owner': 'Ada', 'public': True}",
- 'line': 32,
- 'kind': 'cell'},
- {'prose': ['A function can combine explicit parameters, `*args`, and `**kwargs`. Put the flexible parts '
- 'last so the fixed shape remains visible.'],
- 'code': 'def report(title, *items, **metadata):\n'
- ' print(title)\n'
- ' print(items)\n'
- ' print(metadata)\n'
- '\n'
- 'report("scores", 10, 9, owner="Ada")',
- 'output': "scores\n(10, 9)\n{'owner': 'Ada'}",
- 'line': 47,
- 'kind': 'cell'}]},
- {'slug': 'multiple-return-values',
- 'title': 'Multiple Return Values',
'section': 'Functions',
- 'summary': 'Python returns multiple values by returning a tuple and unpacking it.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/datastructures.html#tuples-and-sequences',
+ 'see_also': [],
+ 'slug': 'args-and-kwargs',
+ 'summary': '*args collects extra positional arguments and **kwargs collects named ones.',
+ 'title': 'Args and Kwargs',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'def total(*numbers):\n return sum(numbers)\n\nprint(total(2, 3, 5))',
+ 'prose': '`*args` collects extra positional arguments into a tuple. This fits '
+ 'functions that naturally accept any number of similar values.'},
+ {'code': 'def describe(**metadata):\n'
+ ' print(metadata)\n'
+ '\n'
+ 'describe(owner="Ada", public=True)',
+ 'prose': '`**kwargs` collects named arguments into a dictionary. The names '
+ 'become string keys.'},
+ {'code': 'def report(title, *items, **metadata):\n'
+ ' print(title)\n'
+ ' print(items)\n'
+ ' print(metadata)\n'
+ '\n'
+ 'report("scores", 10, 9, owner="Ada")',
+ 'prose': 'A function can combine explicit parameters, `*args`, and `**kwargs`. '
+ 'Put the flexible parts last so the fixed shape remains visible.'}]},
+ {'cells': [{'code': 'def divide_with_remainder(total, size):\n'
+ ' quotient = total // size\n'
+ ' remainder = total % size\n'
+ ' return quotient, remainder\n'
+ '\n'
+ 'result = divide_with_remainder(17, 5)\n'
+ 'print(result)',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': '(3, 2)',
+ 'prose': ['Returning values separated by commas returns one tuple. The tuple is '
+ 'visible if the caller stores the result directly.']},
+ {'code': 'boxes, leftover = result\nprint(boxes)\nprint(leftover)',
+ 'kind': 'cell',
+ 'line': 35,
+ 'output': '3\n2',
+ 'prose': ['Callers usually unpack the tuple immediately or soon after. The names at '
+ 'the call site document what each position means.']}],
'code': 'def divide_with_remainder(total, size):\n'
' quotient = total // size\n'
' remainder = total % size\n'
@@ -1866,33 +3764,73 @@
'boxes, leftover = result\n'
'print(boxes)\n'
'print(leftover)\n',
+ 'doc_path': '/tutorial/datastructures.html#tuples-and-sequences',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/datastructures.html#tuples-and-sequences',
'expected_output': '(3, 2)\n3\n2\n',
+ 'explanation': ['Python multiple return values are tuple return values with friendly syntax. '
+ '`return a, b` creates one tuple containing two positions.',
+ 'Most callers unpack that tuple immediately. Good target names make the meaning '
+ 'of each returned position explicit.',
+ 'Use this for small, fixed groups of results. For larger records, a dataclass or '
+ 'named tuple usually communicates better.'],
+ 'min_python': None,
'notes': ['A comma creates a tuple; `return a, b` returns one tuple containing two values.',
'Unpacking at the call site gives each returned position a meaningful name.',
'Use a class-like record when the result has many fields.'],
- 'cells': [{'prose': ['Returning values separated by commas returns one tuple. The tuple is visible if the caller '
- 'stores the result directly.'],
- 'code': 'def divide_with_remainder(total, size):\n'
- ' quotient = total // size\n'
- ' remainder = total % size\n'
- ' return quotient, remainder\n'
+ 'section': 'Functions',
+ 'see_also': [],
+ 'slug': 'multiple-return-values',
+ 'summary': 'Python returns multiple values by returning a tuple and unpacking it.',
+ 'title': 'Multiple Return Values',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'def divide_with_remainder(total, size):\n'
+ ' quotient = total // size\n'
+ ' remainder = total % size\n'
+ ' return quotient, remainder\n'
+ '\n'
+ 'result = divide_with_remainder(17, 5)\n'
+ 'print(result)',
+ 'prose': 'Returning values separated by commas returns one tuple. The tuple is '
+ 'visible if the caller stores the result directly.'},
+ {'code': 'boxes, leftover = result\nprint(boxes)\nprint(leftover)',
+ 'prose': 'Callers usually unpack the tuple immediately or soon after. The names '
+ 'at the call site document what each position means.'}]},
+ {'cells': [{'code': 'def make_multiplier(factor):\n'
+ ' def multiply(value):\n'
+ ' return value * factor\n'
+ ' return multiply\n'
'\n'
- 'result = divide_with_remainder(17, 5)\n'
- 'print(result)',
- 'output': '(3, 2)',
+ 'double = make_multiplier(2)\n'
+ 'print(double(5))',
+ 'kind': 'cell',
'line': 17,
- 'kind': 'cell'},
- {'prose': ['Callers usually unpack the tuple immediately or soon after. The names at the call site '
- 'document what each position means.'],
- 'code': 'boxes, leftover = result\nprint(boxes)\nprint(leftover)',
- 'output': '3\n2',
+ 'output': '10',
+ 'prose': ['Define a function inside another function when the inner behavior needs to '
+ 'remember setup from the outer call. The returned function keeps access to '
+ '`factor`.']},
+ {'code': 'triple = make_multiplier(3)\nprint(triple(5))',
+ 'kind': 'cell',
'line': 35,
- 'kind': 'cell'}]},
- {'slug': 'closures',
- 'title': 'Closures',
- 'section': 'Functions',
- 'summary': 'Inner functions can remember values from an enclosing scope.',
- 'doc_url': 'https://docs.python.org/3.13/reference/executionmodel.html#binding-of-names',
+ 'output': '15',
+ 'prose': ['Calling the outer function again creates a separate closure. `triple` uses '
+ 'the same inner code, but remembers a different `factor`.']},
+ {'code': 'late = []\n'
+ 'for i in range(3):\n'
+ ' late.append(lambda: i)\n'
+ 'print([f() for f in late])\n'
+ '\n'
+ 'bound = []\n'
+ 'for i in range(3):\n'
+ ' bound.append(lambda i=i: i)\n'
+ 'print([f() for f in bound])',
+ 'kind': 'cell',
+ 'line': 48,
+ 'output': '[2, 2, 2]\n[0, 1, 2]',
+ 'prose': ['Closures bind names, not values. Lambdas defined in a loop all reference '
+ 'the same loop variable, so calling them later sees its final value. '
+ 'Capture the value at definition time by binding it as a default argument — '
+ '`lambda i=i: i` — so each closure remembers its own `i`.']}],
'code': 'def make_multiplier(factor):\n'
' def multiply(value):\n'
' return value * factor\n'
@@ -1902,34 +3840,83 @@
'print(double(5))\n'
'\n'
'triple = make_multiplier(3)\n'
- 'print(triple(5))\n',
- 'expected_output': '10\n15\n',
+ 'print(triple(5))\n'
+ '\n'
+ 'late = []\n'
+ 'for i in range(3):\n'
+ ' late.append(lambda: i)\n'
+ 'print([f() for f in late])\n'
+ '\n'
+ 'bound = []\n'
+ 'for i in range(3):\n'
+ ' bound.append(lambda i=i: i)\n'
+ 'print([f() for f in bound])\n',
+ 'doc_path': '/reference/executionmodel.html#binding-of-names',
+ 'doc_url': 'https://docs.python.org/3.13/reference/executionmodel.html#binding-of-names',
+ 'expected_output': '10\n15\n[2, 2, 2]\n[0, 1, 2]\n',
+ 'explanation': ['A closure is a function that remembers names from the scope where it was '
+ 'created. This lets you configure behavior once and call it later.',
+ 'Each call to the outer function creates a separate remembered environment. That '
+ 'is why `double` and `triple` can share the same code but keep different '
+ 'factors.',
+ 'Closures are a foundation for decorators, callbacks, and small function '
+ 'factories.'],
+ 'min_python': None,
'notes': ['A closure keeps access to names from the scope where the inner function was created.',
'Each call to the outer function can create a separate remembered environment.',
- 'Closures are useful for callbacks, small factories, and decorators.'],
- 'cells': [{'prose': ['Define a function inside another function when the inner behavior needs to remember setup from '
- 'the outer call. The returned function keeps access to `factor`.'],
- 'code': 'def make_multiplier(factor):\n'
- ' def multiply(value):\n'
- ' return value * factor\n'
- ' return multiply\n'
+ 'Closures are useful for callbacks, small factories, and decorators.',
+ 'Closures bind names, not values; capture loop variables with `lambda x=x: ...` to '
+ 'freeze them at definition time.'],
+ 'section': 'Functions',
+ 'see_also': [],
+ 'slug': 'closures',
+ 'summary': 'Inner functions can remember values from an enclosing scope.',
+ 'title': 'Closures',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'def make_multiplier(factor):\n'
+ ' def multiply(value):\n'
+ ' return value * factor\n'
+ ' return multiply\n'
+ '\n'
+ 'double = make_multiplier(2)\n'
+ 'print(double(5))',
+ 'prose': 'Define a function inside another function when the inner behavior '
+ 'needs to remember setup from the outer call. The returned function '
+ 'keeps access to `factor`.'},
+ {'code': 'triple = make_multiplier(3)\nprint(triple(5))',
+ 'prose': 'Calling the outer function again creates a separate closure. `triple` '
+ 'uses the same inner code, but remembers a different `factor`.'},
+ {'code': 'late = []\n'
+ 'for i in range(3):\n'
+ ' late.append(lambda: i)\n'
+ 'print([f() for f in late])\n'
+ '\n'
+ 'bound = []\n'
+ 'for i in range(3):\n'
+ ' bound.append(lambda i=i: i)\n'
+ 'print([f() for f in bound])',
+ 'prose': 'Closures bind names, not values. Lambdas defined in a loop all '
+ 'reference the same loop variable, so calling them later sees its '
+ 'final value. Capture the value at definition time by binding it as a '
+ 'default argument — `lambda i=i: i` — so each closure remembers its '
+ 'own `i`.'}]},
+ {'cells': [{'code': 'from functools import partial\n'
'\n'
- 'double = make_multiplier(2)\n'
- 'print(double(5))',
- 'output': '10',
+ '\n'
+ 'def apply_tax(rate, amount):\n'
+ ' return round(amount * (1 + rate), 2)\n'
+ '\n'
+ 'vat = partial(apply_tax, 0.2)\n'
+ 'service_tax = partial(apply_tax, rate=0.1)\n'
+ '\n'
+ 'print(vat(50))\n'
+ 'print(service_tax(amount=80))\n'
+ 'print(vat.func.__name__)',
+ 'kind': 'cell',
'line': 17,
- 'kind': 'cell'},
- {'prose': ['Calling the outer function again creates a separate closure. `triple` uses the same inner '
- 'code, but remembers a different `factor`.'],
- 'code': 'triple = make_multiplier(3)\nprint(triple(5))',
- 'output': '15',
- 'line': 35,
- 'kind': 'cell'}]},
- {'slug': 'partial-functions',
- 'title': 'Partial Functions',
- 'section': 'Functions',
- 'summary': 'functools.partial pre-fills arguments to make a more specific callable.',
- 'doc_url': 'https://docs.python.org/3.13/library/functools.html#functools.partial',
+ 'output': '60.0\n88.0\napply_tax',
+ 'prose': ['A partial object remembers some arguments.']}],
'code': 'from functools import partial\n'
'\n'
'\n'
@@ -1942,31 +3929,71 @@
'print(vat(50))\n'
'print(service_tax(amount=80))\n'
'print(vat.func.__name__)\n',
+ 'doc_path': '/library/functools.html#functools.partial',
+ 'doc_url': 'https://docs.python.org/3.13/library/functools.html#functools.partial',
'expected_output': '60.0\n88.0\napply_tax\n',
+ 'explanation': ['functools.partial pre-fills arguments to make a more specific callable. It '
+ 'exists to make a common boundary explicit instead of leaving the behavior '
+ 'implicit in a larger program.',
+ 'Use it when the problem shape matches the example, and prefer simpler '
+ 'neighboring tools when the extra machinery would hide the intent. The notes '
+ 'call out the boundary so the feature stays practical rather than decorative.',
+ 'The example is small, deterministic, and focused on the semantic point. The '
+ 'complete source is editable below, while the walkthrough pairs the source with '
+ 'its output.'],
+ 'min_python': None,
'notes': ['A partial object remembers some arguments.',
'The resulting callable can be passed where an ordinary function is expected.',
'Prefer a named function when the pre-filled behavior needs richer logic.'],
- 'cells': [{'prose': ['A partial object remembers some arguments.'],
- 'code': 'from functools import partial\n'
- '\n'
+ 'section': 'Functions',
+ 'see_also': [],
+ 'slug': 'partial-functions',
+ 'summary': 'functools.partial pre-fills arguments to make a more specific callable.',
+ 'title': 'Partial Functions',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'from functools import partial\n'
+ '\n'
+ '\n'
+ 'def apply_tax(rate, amount):\n'
+ ' return round(amount * (1 + rate), 2)\n'
+ '\n'
+ 'vat = partial(apply_tax, 0.2)\n'
+ 'service_tax = partial(apply_tax, rate=0.1)\n'
+ '\n'
+ 'print(vat(50))\n'
+ 'print(service_tax(amount=80))\n'
+ 'print(vat.func.__name__)',
+ 'prose': 'A partial object remembers some arguments.'}]},
+ {'cells': [{'code': 'count = 0\n'
+ '\n'
+ 'def bump_global():\n'
+ ' global count\n'
+ ' count += 1\n'
+ '\n'
+ 'bump_global()\n'
+ 'print(count)',
+ 'kind': 'cell',
+ 'line': 22,
+ 'output': '1',
+ 'prose': ['`global` tells assignment to update a module-level binding. Without it, '
+ '`count += 1` would try to assign a local `count`.']},
+ {'code': 'def make_counter():\n'
+ ' total = 0\n'
+ ' def bump():\n'
+ ' nonlocal total\n'
+ ' total += 1\n'
+ ' return total\n'
+ ' return bump\n'
'\n'
- 'def apply_tax(rate, amount):\n'
- ' return round(amount * (1 + rate), 2)\n'
- '\n'
- 'vat = partial(apply_tax, 0.2)\n'
- 'service_tax = partial(apply_tax, rate=0.1)\n'
- '\n'
- 'print(vat(50))\n'
- 'print(service_tax(amount=80))\n'
- 'print(vat.func.__name__)',
- 'output': '60.0\n88.0\napply_tax',
- 'line': 17,
- 'kind': 'cell'}]},
- {'slug': 'scope-global-nonlocal',
- 'title': 'Global and Nonlocal',
- 'section': 'Functions',
- 'summary': 'global and nonlocal choose which outer binding assignment should update.',
- 'doc_url': 'https://docs.python.org/3.13/reference/simple_stmts.html#the-global-statement',
+ 'counter = make_counter()\n'
+ 'print(counter())\n'
+ 'print(counter())',
+ 'kind': 'cell',
+ 'line': 41,
+ 'output': '1\n2',
+ 'prose': ['`nonlocal` tells assignment to update a binding in the nearest enclosing '
+ 'function scope. This is useful for small closures that keep state.']}],
'code': 'count = 0\n'
'\n'
'def bump_global():\n'
@@ -1988,37 +4015,80 @@
'counter = make_counter()\n'
'print(counter())\n'
'print(counter())\n',
+ 'doc_path': '/reference/simple_stmts.html#the-global-statement',
+ 'doc_url': 'https://docs.python.org/3.13/reference/simple_stmts.html#the-global-statement',
'expected_output': '1\n1\n2\n',
+ 'explanation': ['Assignment normally creates or updates a local name inside the current '
+ 'function. `global` and `nonlocal` are explicit escape hatches for rebinding '
+ 'names outside that local scope.',
+ 'Use `nonlocal` when an inner function should update a name in an enclosing '
+ 'function. Use `global` rarely; passing values and returning results is usually '
+ 'clearer.',
+ 'These statements affect name binding, not object mutation. Mutating a shared '
+ 'list is different from rebinding the name itself.'],
+ 'min_python': None,
'notes': ['Assignment inside a function is local unless declared otherwise.',
- 'Prefer `nonlocal` for closure state and avoid `global` unless module state is truly intended.',
- 'Passing values and returning results is usually easier to test than rebinding outer names.'],
- 'cells': [{'prose': ['`global` tells assignment to update a module-level binding. Without it, `count += 1` would try '
- 'to assign a local `count`.'],
- 'code': 'count = 0\n\ndef bump_global():\n global count\n count += 1\n\nbump_global()\nprint(count)',
- 'output': '1',
- 'line': 22,
- 'kind': 'cell'},
- {'prose': ['`nonlocal` tells assignment to update a binding in the nearest enclosing function scope. This '
- 'is useful for small closures that keep state.'],
- 'code': 'def make_counter():\n'
- ' total = 0\n'
- ' def bump():\n'
- ' nonlocal total\n'
- ' total += 1\n'
- ' return total\n'
- ' return bump\n'
- '\n'
- 'counter = make_counter()\n'
- 'print(counter())\n'
- 'print(counter())',
- 'output': '1\n2',
- 'line': 41,
- 'kind': 'cell'}]},
- {'slug': 'recursion',
- 'title': 'Recursion',
+ 'Prefer `nonlocal` for closure state and avoid `global` unless module state is truly '
+ 'intended.',
+ 'Passing values and returning results is usually easier to test than rebinding outer '
+ 'names.'],
'section': 'Functions',
- 'summary': 'Recursive functions solve nested problems by calling themselves on smaller pieces.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#defining-functions',
+ 'see_also': ['variables', 'closures', 'functions'],
+ 'slug': 'scope-global-nonlocal',
+ 'summary': 'global and nonlocal choose which outer binding assignment should update.',
+ 'title': 'Global and Nonlocal',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'count = 0\n'
+ '\n'
+ 'def bump_global():\n'
+ ' global count\n'
+ ' count += 1\n'
+ '\n'
+ 'bump_global()\n'
+ 'print(count)',
+ 'prose': '`global` tells assignment to update a module-level binding. Without '
+ 'it, `count += 1` would try to assign a local `count`.'},
+ {'code': 'def make_counter():\n'
+ ' total = 0\n'
+ ' def bump():\n'
+ ' nonlocal total\n'
+ ' total += 1\n'
+ ' return total\n'
+ ' return bump\n'
+ '\n'
+ 'counter = make_counter()\n'
+ 'print(counter())\n'
+ 'print(counter())',
+ 'prose': '`nonlocal` tells assignment to update a binding in the nearest '
+ 'enclosing function scope. This is useful for small closures that keep '
+ 'state.'}]},
+ {'cells': [{'code': 'def total(node):\n'
+ ' subtotal = node["value"]\n'
+ ' for child in node["children"]:\n'
+ ' subtotal += total(child)\n'
+ ' return subtotal\n'
+ '\n'
+ 'print(total({"value": 2, "children": []}))',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': '2',
+ 'prose': ['A leaf node is the base case. It has no children, so the function can '
+ 'return its own value without making another recursive call.']},
+ {'code': 'tree = {\n'
+ ' "value": 1,\n'
+ ' "children": [\n'
+ ' {"value": 2, "children": []},\n'
+ ' {"value": 3, "children": [{"value": 4, "children": []}]},\n'
+ ' ],\n'
+ '}\n'
+ '\n'
+ 'print(total(tree))',
+ 'kind': 'cell',
+ 'line': 35,
+ 'output': '10',
+ 'prose': ['A non-leaf node solves the same problem for each child, then combines '
+ 'those smaller totals with its own value.']}],
'code': 'tree = {\n'
' "value": 1,\n'
' "children": [\n'
@@ -2035,41 +4105,70 @@
'\n'
'print(total({"value": 2, "children": []}))\n'
'print(total(tree))\n',
+ 'doc_path': '/tutorial/controlflow.html#defining-functions',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#defining-functions',
'expected_output': '2\n10\n',
+ 'explanation': ['A recursive function calls itself to solve a smaller piece of the same problem. '
+ 'Recursion exists for data that is naturally nested: trees, menus, expression '
+ 'nodes, and directory-like structures.',
+ 'Every recursive function needs a base case that can be answered directly. The '
+ 'recursive case must move toward that base case by passing a smaller part of the '
+ 'data.',
+ 'Prefer loops for simple repetition over a flat sequence. Prefer recursion when '
+ 'the data shape is recursive too.'],
+ 'min_python': None,
'notes': ['Every recursive function needs a base case that stops the calls.',
'Recursion fits nested data better than flat repetition.',
- 'Python limits recursion depth, so loops are often better for very deep or simple repetition.'],
- 'cells': [{'prose': ['A leaf node is the base case. It has no children, so the function can return its own value '
- 'without making another recursive call.'],
- 'code': 'def total(node):\n'
- ' subtotal = node["value"]\n'
- ' for child in node["children"]:\n'
- ' subtotal += total(child)\n'
- ' return subtotal\n'
- '\n'
- 'print(total({"value": 2, "children": []}))',
- 'output': '2',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['A non-leaf node solves the same problem for each child, then combines those smaller totals '
- 'with its own value.'],
- 'code': 'tree = {\n'
- ' "value": 1,\n'
- ' "children": [\n'
- ' {"value": 2, "children": []},\n'
- ' {"value": 3, "children": [{"value": 4, "children": []}]},\n'
- ' ],\n'
- '}\n'
- '\n'
- 'print(total(tree))',
- 'output': '10',
- 'line': 35,
- 'kind': 'cell'}]},
- {'slug': 'lambdas',
- 'title': 'Lambdas',
+ 'Python limits recursion depth, so loops are often better for very deep or simple '
+ 'repetition.'],
'section': 'Functions',
- 'summary': 'lambda creates small anonymous function expressions.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#lambda-expressions',
+ 'see_also': [],
+ 'slug': 'recursion',
+ 'summary': 'Recursive functions solve nested problems by calling themselves on smaller pieces.',
+ 'title': 'Recursion',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'def total(node):\n'
+ ' subtotal = node["value"]\n'
+ ' for child in node["children"]:\n'
+ ' subtotal += total(child)\n'
+ ' return subtotal\n'
+ '\n'
+ 'print(total({"value": 2, "children": []}))',
+ 'prose': 'A leaf node is the base case. It has no children, so the function can '
+ 'return its own value without making another recursive call.'},
+ {'code': 'tree = {\n'
+ ' "value": 1,\n'
+ ' "children": [\n'
+ ' {"value": 2, "children": []},\n'
+ ' {"value": 3, "children": [{"value": 4, "children": []}]},\n'
+ ' ],\n'
+ '}\n'
+ '\n'
+ 'print(total(tree))',
+ 'prose': 'A non-leaf node solves the same problem for each child, then combines '
+ 'those smaller totals with its own value.'}]},
+ {'cells': [{'code': 'add_tax = lambda price: round(price * 1.08, 2)\nprint(add_tax(10))',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': '10.8',
+ 'prose': ['A lambda is a function expression. Assigning one to a name works, although '
+ '`def` is usually clearer for reusable behavior.']},
+ {'code': 'items = [("notebook", 5), ("pen", 2), ("bag", 20)]\n'
+ 'by_price = sorted(items, key=lambda item: item[1])\n'
+ 'print(by_price)',
+ 'kind': 'cell',
+ 'line': 30,
+ 'output': "[('pen', 2), ('notebook', 5), ('bag', 20)]",
+ 'prose': ['Lambdas are most idiomatic when passed directly to another function. '
+ '`sorted()` calls this key function once for each item.']},
+ {'code': 'def price(item):\n return item[1]\n\nprint(sorted(items, key=price))',
+ 'kind': 'cell',
+ 'line': 44,
+ 'output': "[('pen', 2), ('notebook', 5), ('bag', 20)]",
+ 'prose': ['A named function is better when the behavior should be reused or '
+ 'explained. It produces the same sort key, but gives the operation a '
+ 'name.']}],
'code': 'add_tax = lambda price: round(price * 1.08, 2)\n'
'print(add_tax(10))\n'
'\n'
@@ -2081,35 +4180,103 @@
' return item[1]\n'
'\n'
'print(sorted(items, key=price))\n',
- 'expected_output': "10.8\n[('pen', 2), ('notebook', 5), ('bag', 20)]\n[('pen', 2), ('notebook', 5), ('bag', 20)]\n",
+ 'doc_path': '/tutorial/controlflow.html#lambda-expressions',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/controlflow.html#lambda-expressions',
+ 'expected_output': '10.8\n'
+ "[('pen', 2), ('notebook', 5), ('bag', 20)]\n"
+ "[('pen', 2), ('notebook', 5), ('bag', 20)]\n",
+ 'explanation': ['`lambda` creates a small anonymous function expression. It is most useful when '
+ 'Python asks for a function and the behavior is short enough to read inline.',
+ 'A lambda can only contain one expression. Use `def` when the behavior deserves '
+ 'a name, needs statements, or would be easier to test separately.',
+ 'Lambdas often appear as key functions, callbacks, and tiny adapters. Keep them '
+ 'simple enough that the call site remains clearer than a named helper.'],
+ 'min_python': None,
'notes': ['Lambdas are expressions, not statements.',
'Prefer `def` for multi-step or reused behavior.',
'Lambdas are common as `key=` functions because the behavior is local to one call.'],
- 'cells': [{'prose': ['A lambda is a function expression. Assigning one to a name works, although `def` is usually '
- 'clearer for reusable behavior.'],
- 'code': 'add_tax = lambda price: round(price * 1.08, 2)\nprint(add_tax(10))',
- 'output': '10.8',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['Lambdas are most idiomatic when passed directly to another function. `sorted()` calls this key '
- 'function once for each item.'],
- 'code': 'items = [("notebook", 5), ("pen", 2), ("bag", 20)]\n'
- 'by_price = sorted(items, key=lambda item: item[1])\n'
- 'print(by_price)',
- 'output': "[('pen', 2), ('notebook', 5), ('bag', 20)]",
- 'line': 30,
- 'kind': 'cell'},
- {'prose': ['A named function is better when the behavior should be reused or explained. It produces the '
- 'same sort key, but gives the operation a name.'],
- 'code': 'def price(item):\n return item[1]\n\nprint(sorted(items, key=price))',
- 'output': "[('pen', 2), ('notebook', 5), ('bag', 20)]",
- 'line': 44,
- 'kind': 'cell'}]},
- {'slug': 'generators',
- 'title': 'Generators',
- 'section': 'Iteration',
- 'summary': 'yield creates an iterator that produces values on demand.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/classes.html#generators',
+ 'section': 'Functions',
+ 'see_also': [],
+ 'slug': 'lambdas',
+ 'summary': 'lambda creates small anonymous function expressions.',
+ 'title': 'Lambdas',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'add_tax = lambda price: round(price * 1.08, 2)\nprint(add_tax(10))',
+ 'prose': 'A lambda is a function expression. Assigning one to a name works, '
+ 'although `def` is usually clearer for reusable behavior.'},
+ {'code': 'items = [("notebook", 5), ("pen", 2), ("bag", 20)]\n'
+ 'by_price = sorted(items, key=lambda item: item[1])\n'
+ 'print(by_price)',
+ 'prose': 'Lambdas are most idiomatic when passed directly to another function. '
+ '`sorted()` calls this key function once for each item.'},
+ {'code': 'def price(item):\n'
+ ' return item[1]\n'
+ '\n'
+ 'print(sorted(items, key=price))',
+ 'prose': 'A named function is better when the behavior should be reused or '
+ 'explained. It produces the same sort key, but gives the operation a '
+ 'name.'}]},
+ {'cells': [{'code': 'def countdown(n):\n'
+ ' while n > 0:\n'
+ ' yield n\n'
+ ' n -= 1\n'
+ '\n'
+ 'numbers = countdown(3)\n'
+ 'print(next(numbers))\n'
+ 'print(next(numbers))',
+ 'kind': 'cell',
+ 'line': 22,
+ 'output': '3\n2',
+ 'prose': ['Calling a generator function returns an iterator. `next()` asks for one '
+ 'value and resumes the function until the next `yield`.']},
+ {'code': 'for value in countdown(3):\n print(value)',
+ 'kind': 'cell',
+ 'line': 42,
+ 'output': '3\n2\n1',
+ 'prose': ['A `for` loop repeatedly calls `next()` for you. The loop stops when the '
+ 'generator is exhausted.']},
+ {'code': 'def countdown_eager(n):\n'
+ ' result = []\n'
+ ' while n > 0:\n'
+ ' result.append(n)\n'
+ ' n -= 1\n'
+ ' return result\n'
+ '\n'
+ 'values = countdown_eager(3)\n'
+ 'print(values)\n'
+ 'print(values)\n'
+ '\n'
+ 'stream = countdown(3)\n'
+ 'print(list(stream))\n'
+ 'print(list(stream))',
+ 'kind': 'cell',
+ 'line': 57,
+ 'output': '[3, 2, 1]\n[3, 2, 1]\n[3, 2, 1]\n[]',
+ 'prose': ['`return` builds the entire result before handing it back; `yield` produces '
+ 'values on demand. The list keeps its values for repeated use, while the '
+ 'generator is exhausted after one pass.']},
+ {'code': 'class Countdown:\n'
+ ' def __init__(self, n):\n'
+ ' self.n = n\n'
+ '\n'
+ ' def __iter__(self):\n'
+ ' return self\n'
+ '\n'
+ ' def __next__(self):\n'
+ ' if self.n <= 0:\n'
+ ' raise StopIteration\n'
+ ' value = self.n\n'
+ ' self.n -= 1\n'
+ ' return value\n'
+ '\n'
+ 'print(list(Countdown(3)))',
+ 'kind': 'cell',
+ 'line': 85,
+ 'output': '[3, 2, 1]',
+ 'prose': ['Every generator is an iterator. The same countdown written by hand needs '
+ '`__iter__` and `__next__` and an explicit `StopIteration`. The generator '
+ 'function expresses the same protocol with one `yield`.']}],
'code': 'def countdown(n):\n'
' while n > 0:\n'
' yield n\n'
@@ -2120,37 +4287,131 @@
'print(next(numbers))\n'
'\n'
'for value in countdown(3):\n'
- ' print(value)\n',
- 'expected_output': '3\n2\n3\n2\n1\n',
- 'notes': ['Generator functions are a concise way to create custom iterators.',
- 'Values are produced on demand.',
+ ' print(value)\n'
+ '\n'
+ 'def countdown_eager(n):\n'
+ ' result = []\n'
+ ' while n > 0:\n'
+ ' result.append(n)\n'
+ ' n -= 1\n'
+ ' return result\n'
+ '\n'
+ 'values = countdown_eager(3)\n'
+ 'print(values)\n'
+ 'print(values)\n'
+ '\n'
+ 'stream = countdown(3)\n'
+ 'print(list(stream))\n'
+ 'print(list(stream))\n'
+ '\n'
+ 'class Countdown:\n'
+ ' def __init__(self, n):\n'
+ ' self.n = n\n'
+ '\n'
+ ' def __iter__(self):\n'
+ ' return self\n'
+ '\n'
+ ' def __next__(self):\n'
+ ' if self.n <= 0:\n'
+ ' raise StopIteration\n'
+ ' value = self.n\n'
+ ' self.n -= 1\n'
+ ' return value\n'
+ '\n'
+ 'print(list(Countdown(3)))\n',
+ 'doc_path': '/tutorial/classes.html#generators',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/classes.html#generators',
+ 'expected_output': '3\n2\n3\n2\n1\n[3, 2, 1]\n[3, 2, 1]\n[3, 2, 1]\n[]\n[3, 2, 1]\n',
+ 'explanation': ['A generator function is a convenient way to write your own iterator. `yield` '
+ 'produces one value, pauses the function, and resumes when the next value is '
+ 'requested.',
+ 'Generators are useful for pipelines, large inputs, and infinite sequences '
+ 'because they avoid building an entire collection in memory.',
+ 'Use `next()` to request one value manually, or loop over the generator to '
+ 'consume values until it is exhausted.'],
+ 'min_python': None,
+ 'notes': ['Generator functions are a concise way to create custom iterators; every generator is '
+ 'an iterator.',
+ '`yield` defers work and streams values; `return` produces the whole result up front.',
'A generator is consumed as you iterate over it.',
- 'Prefer a list when you need to reuse stored results; prefer a generator when values can be streamed '
- 'once.'],
- 'cells': [{'prose': ['Calling a generator function returns an iterator. `next()` asks for one value and resumes the '
- 'function until the next `yield`.'],
- 'code': 'def countdown(n):\n'
- ' while n > 0:\n'
- ' yield n\n'
- ' n -= 1\n'
- '\n'
- 'numbers = countdown(3)\n'
- 'print(next(numbers))\n'
- 'print(next(numbers))',
- 'output': '3\n2',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['A `for` loop repeatedly calls `next()` for you. The loop stops when the generator is '
- 'exhausted.'],
- 'code': 'for value in countdown(3):\n print(value)',
- 'output': '3\n2\n1',
- 'line': 37,
- 'kind': 'cell'}]},
- {'slug': 'yield-from',
- 'title': 'Yield From',
+ 'Prefer a list when you need to reuse stored results; prefer a generator when values '
+ 'can be streamed once.'],
'section': 'Iteration',
- 'summary': 'yield from delegates part of a generator to another iterable.',
- 'doc_url': 'https://docs.python.org/3.13/reference/expressions.html#yield-expressions',
+ 'see_also': ['iterators', 'iterator-vs-iterable', 'generator-expressions'],
+ 'slug': 'generators',
+ 'summary': 'yield creates an iterator that produces values on demand.',
+ 'title': 'Generators',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'def countdown(n):\n'
+ ' while n > 0:\n'
+ ' yield n\n'
+ ' n -= 1\n'
+ '\n'
+ 'numbers = countdown(3)\n'
+ 'print(next(numbers))\n'
+ 'print(next(numbers))',
+ 'prose': 'Calling a generator function returns an iterator. `next()` asks for '
+ 'one value and resumes the function until the next `yield`.'},
+ {'code': 'for value in countdown(3):\n print(value)',
+ 'prose': 'A `for` loop repeatedly calls `next()` for you. The loop stops when '
+ 'the generator is exhausted.'},
+ {'code': 'def countdown_eager(n):\n'
+ ' result = []\n'
+ ' while n > 0:\n'
+ ' result.append(n)\n'
+ ' n -= 1\n'
+ ' return result\n'
+ '\n'
+ 'values = countdown_eager(3)\n'
+ 'print(values)\n'
+ 'print(values)\n'
+ '\n'
+ 'stream = countdown(3)\n'
+ 'print(list(stream))\n'
+ 'print(list(stream))',
+ 'prose': '`return` builds the entire result before handing it back; `yield` '
+ 'produces values on demand. The list keeps its values for repeated '
+ 'use, while the generator is exhausted after one pass.'},
+ {'code': 'class Countdown:\n'
+ ' def __init__(self, n):\n'
+ ' self.n = n\n'
+ '\n'
+ ' def __iter__(self):\n'
+ ' return self\n'
+ '\n'
+ ' def __next__(self):\n'
+ ' if self.n <= 0:\n'
+ ' raise StopIteration\n'
+ ' value = self.n\n'
+ ' self.n -= 1\n'
+ ' return value\n'
+ '\n'
+ 'print(list(Countdown(3)))',
+ 'prose': 'Every generator is an iterator. The same countdown written by hand '
+ 'needs `__iter__` and `__next__` and an explicit `StopIteration`. The '
+ 'generator function expresses the same protocol with one `yield`.'}]},
+ {'cells': [{'code': 'def page():\n'
+ ' yield "header"\n'
+ ' yield from ["intro", "body"]\n'
+ ' yield "footer"\n'
+ '\n'
+ 'print(list(page()))',
+ 'kind': 'cell',
+ 'line': 22,
+ 'output': "['header', 'intro', 'body', 'footer']",
+ 'prose': ['`yield from` delegates to another iterable. The caller receives one stream '
+ 'even though part of it came from a list.']},
+ {'code': 'def flatten(rows):\n'
+ ' for row in rows:\n'
+ ' yield from row\n'
+ '\n'
+ 'print(list(flatten([[1, 2], [3]])))',
+ 'kind': 'cell',
+ 'line': 39,
+ 'output': '[1, 2, 3]',
+ 'prose': ['Delegation is useful when flattening nested iterables. `yield from row` '
+ 'replaces an inner loop that would yield each item by hand.']}],
'code': 'def page():\n'
' yield "header"\n'
' yield from ["intro", "body"]\n'
@@ -2164,36 +4425,66 @@
' yield from row\n'
'\n'
'print(list(flatten([[1, 2], [3]])))\n',
+ 'doc_path': '/reference/expressions.html#yield-expressions',
+ 'doc_url': 'https://docs.python.org/3.13/reference/expressions.html#yield-expressions',
'expected_output': "['header', 'intro', 'body', 'footer']\n[1, 2, 3]\n",
+ 'explanation': ['`yield from` lets one generator yield every value from another iterable. It is '
+ 'a compact way to delegate part of a stream.',
+ 'Use it when a generator is mostly stitching together other iterables or '
+ 'sub-generators. It keeps the producer pipeline visible without writing a nested '
+ '`for` loop.',
+ 'The consumer still sees one stream of values.'],
+ 'min_python': None,
'notes': ['`yield from iterable` yields each value from that iterable.',
'It keeps generator pipelines compact.',
'Use a plain `yield` when producing one value directly.'],
- 'cells': [{'prose': ['`yield from` delegates to another iterable. The caller receives one stream even though part of '
- 'it came from a list.'],
- 'code': 'def page():\n'
- ' yield "header"\n'
- ' yield from ["intro", "body"]\n'
- ' yield "footer"\n'
- '\n'
- 'print(list(page()))',
- 'output': "['header', 'intro', 'body', 'footer']",
- 'line': 22,
- 'kind': 'cell'},
- {'prose': ['Delegation is useful when flattening nested iterables. `yield from row` replaces an inner loop '
- 'that would yield each item by hand.'],
- 'code': 'def flatten(rows):\n'
- ' for row in rows:\n'
- ' yield from row\n'
- '\n'
- 'print(list(flatten([[1, 2], [3]])))',
- 'output': '[1, 2, 3]',
- 'line': 39,
- 'kind': 'cell'}]},
- {'slug': 'generator-expressions',
- 'title': 'Generator Expressions',
'section': 'Iteration',
- 'summary': 'Generator expressions use comprehension-like syntax to stream values lazily.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/classes.html#generator-expressions',
+ 'see_also': ['generators', 'generator-expressions', 'itertools'],
+ 'slug': 'yield-from',
+ 'summary': 'yield from delegates part of a generator to another iterable.',
+ 'title': 'Yield From',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'def page():\n'
+ ' yield "header"\n'
+ ' yield from ["intro", "body"]\n'
+ ' yield "footer"\n'
+ '\n'
+ 'print(list(page()))',
+ 'prose': '`yield from` delegates to another iterable. The caller receives one '
+ 'stream even though part of it came from a list.'},
+ {'code': 'def flatten(rows):\n'
+ ' for row in rows:\n'
+ ' yield from row\n'
+ '\n'
+ 'print(list(flatten([[1, 2], [3]])))',
+ 'prose': 'Delegation is useful when flattening nested iterables. `yield from '
+ 'row` replaces an inner loop that would yield each item by hand.'}]},
+ {'cells': [{'code': 'numbers = [1, 2, 3, 4]\n'
+ 'list_squares = [number * number for number in numbers]\n'
+ 'print(list_squares)',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': '[1, 4, 9, 16]',
+ 'prose': ['A list comprehension is eager: it builds a list immediately. That is '
+ 'useful when you need to store or reuse the results.']},
+ {'code': 'stream_squares = (number * number for number in numbers)\n'
+ 'print(next(stream_squares))\n'
+ 'print(next(stream_squares))\n'
+ 'print(list(stream_squares))',
+ 'kind': 'cell',
+ 'line': 31,
+ 'output': '1\n4\n[9, 16]',
+ 'prose': ['A generator expression is lazy: it creates an iterator that produces '
+ 'values as they are consumed. After two `next()` calls, only the remaining '
+ 'squares are left.']},
+ {'code': 'print(sum(number * number for number in numbers))',
+ 'kind': 'cell',
+ 'line': 48,
+ 'output': '30',
+ 'prose': ['Generator expressions are common inside reducing functions. When a '
+ 'generator expression is the only argument, the extra parentheses can be '
+ 'omitted.']}],
'code': 'numbers = [1, 2, 3, 4]\n'
'list_squares = [number * number for number in numbers]\n'
'print(list_squares)\n'
@@ -2204,38 +4495,66 @@
'print(list(stream_squares))\n'
'\n'
'print(sum(number * number for number in numbers))\n',
+ 'doc_path': '/tutorial/classes.html#generator-expressions',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/classes.html#generator-expressions',
'expected_output': '[1, 4, 9, 16]\n1\n4\n[9, 16]\n30\n',
+ 'explanation': ['Generator expressions look like list comprehensions with parentheses, but they '
+ 'produce an iterator instead of building a concrete collection immediately.',
+ 'Use them when a consumer such as `sum()`, `any()`, or a `for` loop can use '
+ 'values one at a time. This keeps the transformation close to the consumer and '
+ 'avoids storing intermediate lists.',
+ 'Like other iterators, a generator expression is consumed as values are '
+ 'requested. Create a new generator expression when you need another pass.'],
+ 'min_python': None,
'notes': ['List, dict, and set comprehensions build concrete collections.',
'Generator expressions produce one-pass iterators.',
'Use generator expressions when the consumer can process values one at a time.'],
- 'cells': [{'prose': ['A list comprehension is eager: it builds a list immediately. That is useful when you need to '
- 'store or reuse the results.'],
- 'code': 'numbers = [1, 2, 3, 4]\n'
- 'list_squares = [number * number for number in numbers]\n'
- 'print(list_squares)',
- 'output': '[1, 4, 9, 16]',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['A generator expression is lazy: it creates an iterator that produces values as they are '
- 'consumed. After two `next()` calls, only the remaining squares are left.'],
- 'code': 'stream_squares = (number * number for number in numbers)\n'
- 'print(next(stream_squares))\n'
- 'print(next(stream_squares))\n'
- 'print(list(stream_squares))',
- 'output': '1\n4\n[9, 16]',
- 'line': 31,
- 'kind': 'cell'},
- {'prose': ['Generator expressions are common inside reducing functions. When a generator expression is the '
- 'only argument, the extra parentheses can be omitted.'],
- 'code': 'print(sum(number * number for number in numbers))',
- 'output': '30',
- 'line': 48,
- 'kind': 'cell'}]},
- {'slug': 'itertools',
- 'title': 'Itertools',
'section': 'Iteration',
- 'summary': 'itertools composes lazy iterator streams.',
- 'doc_url': 'https://docs.python.org/3.13/library/itertools.html',
+ 'see_also': [],
+ 'slug': 'generator-expressions',
+ 'summary': 'Generator expressions use comprehension-like syntax to stream values lazily.',
+ 'title': 'Generator Expressions',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'numbers = [1, 2, 3, 4]\n'
+ 'list_squares = [number * number for number in numbers]\n'
+ 'print(list_squares)',
+ 'prose': 'A list comprehension is eager: it builds a list immediately. That is '
+ 'useful when you need to store or reuse the results.'},
+ {'code': 'stream_squares = (number * number for number in numbers)\n'
+ 'print(next(stream_squares))\n'
+ 'print(next(stream_squares))\n'
+ 'print(list(stream_squares))',
+ 'prose': 'A generator expression is lazy: it creates an iterator that produces '
+ 'values as they are consumed. After two `next()` calls, only the '
+ 'remaining squares are left.'},
+ {'code': 'print(sum(number * number for number in numbers))',
+ 'prose': 'Generator expressions are common inside reducing functions. When a '
+ 'generator expression is the only argument, the extra parentheses can '
+ 'be omitted.'}]},
+ {'cells': [{'code': 'import itertools\n'
+ '\n'
+ 'counter = itertools.count(10)\n'
+ 'print(list(itertools.islice(counter, 3)))',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': '[10, 11, 12]',
+ 'prose': ['`count()` can produce values forever, so `islice()` takes a finite window. '
+ 'Nothing is materialized until `list()` consumes the iterator.']},
+ {'code': 'pages = itertools.chain(["intro", "setup"], ["deploy"])\nprint(list(pages))',
+ 'kind': 'cell',
+ 'line': 32,
+ 'output': "['intro', 'setup', 'deploy']",
+ 'prose': ['`chain()` presents several iterables as one stream. This avoids building '
+ 'an intermediate list just to loop over combined inputs.']},
+ {'code': 'scores = [7, 10, 8, 10]\n'
+ 'high_scores = itertools.compress(scores, [score >= 9 for score in scores])\n'
+ 'print(list(high_scores))',
+ 'kind': 'cell',
+ 'line': 45,
+ 'output': '[10, 10]',
+ 'prose': ['Iterator helpers compose with ordinary Python expressions. `compress()` '
+ 'keeps items whose corresponding selector is true.']}],
'code': 'import itertools\n'
'\n'
'counter = itertools.count(10)\n'
@@ -2247,66 +4566,48 @@
'scores = [7, 10, 8, 10]\n'
'high_scores = itertools.compress(scores, [score >= 9 for score in scores])\n'
'print(list(high_scores))\n',
+ 'doc_path': '/library/itertools.html',
+ 'doc_url': 'https://docs.python.org/3.13/library/itertools.html',
'expected_output': "[10, 11, 12]\n['intro', 'setup', 'deploy']\n[10, 10]\n",
+ 'explanation': ['The `itertools` module contains tools for composing iterator streams: '
+ 'combining, slicing, grouping, and repeating values without changing the '
+ 'consumer protocol.',
+ 'Many `itertools` functions are lazy. They describe work to do later instead of '
+ 'building a list immediately, so helpers such as `islice()` are useful when '
+ 'taking a finite window.',
+ 'Iterator pipelines let each step stay small: one object produces values, '
+ 'another transforms them, and a final consumer such as `list()` or a loop pulls '
+ 'values through the pipeline.'],
+ 'min_python': None,
'notes': ['`itertools` composes producer and transformer streams.',
'Iterator pipelines avoid building intermediate lists.',
'Use `islice()` to take a finite piece from an infinite iterator.',
'Convert to a list only when you need concrete results.'],
- 'cells': [{'prose': ['`count()` can produce values forever, so `islice()` takes a finite window. Nothing is '
- 'materialized until `list()` consumes the iterator.'],
- 'code': 'import itertools\n\ncounter = itertools.count(10)\nprint(list(itertools.islice(counter, 3)))',
- 'output': '[10, 11, 12]',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['`chain()` presents several iterables as one stream. This avoids building an intermediate list '
- 'just to loop over combined inputs.'],
- 'code': 'pages = itertools.chain(["intro", "setup"], ["deploy"])\nprint(list(pages))',
- 'output': "['intro', 'setup', 'deploy']",
- 'line': 32,
- 'kind': 'cell'},
- {'prose': ['Iterator helpers compose with ordinary Python expressions. `compress()` keeps items whose '
- 'corresponding selector is true.'],
- 'code': 'scores = [7, 10, 8, 10]\n'
- 'high_scores = itertools.compress(scores, [score >= 9 for score in scores])\n'
- 'print(list(high_scores))',
- 'output': '[10, 10]',
- 'line': 45,
- 'kind': 'cell'}]},
- {'slug': 'decorators',
- 'title': 'Decorators',
- 'section': 'Functions',
- 'summary': 'Decorators wrap or register functions using @ syntax.',
- 'doc_url': 'https://docs.python.org/3.13/glossary.html#term-decorator',
- 'code': 'from functools import wraps\n'
- '\n'
- '\n'
- 'def loud(func):\n'
- ' @wraps(func)\n'
- ' def wrapper(name):\n'
- ' return func(name).upper()\n'
- ' return wrapper\n'
- '\n'
- '\n'
- 'def greet(name):\n'
- ' return f"hello {name}"\n'
- '\n'
- 'manual_greet = loud(greet)\n'
- 'print(manual_greet("python"))\n'
- '\n'
- '@loud\n'
- 'def welcome(name):\n'
- ' """Return a welcome message."""\n'
- ' return f"welcome {name}"\n'
- '\n'
- 'print(welcome("workers"))\n'
- 'print(welcome.__name__)\n',
- 'expected_output': 'HELLO PYTHON\nWELCOME WORKERS\nwelcome\n',
- 'notes': ['`@decorator` is shorthand for assigning `func = decorator(func)`.',
- 'Decorators can wrap, replace, or register functions.',
- 'Use `functools.wraps` in production wrappers that should preserve metadata.'],
- 'cells': [{'prose': ['A decorator is just a function that takes a function and returns another callable. Applying it '
- 'manually shows the wrapping step.'],
- 'code': 'from functools import wraps\n'
+ 'section': 'Iteration',
+ 'see_also': [],
+ 'slug': 'itertools',
+ 'summary': 'itertools composes lazy iterator streams.',
+ 'title': 'Itertools',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'import itertools\n'
+ '\n'
+ 'counter = itertools.count(10)\n'
+ 'print(list(itertools.islice(counter, 3)))',
+ 'prose': '`count()` can produce values forever, so `islice()` takes a finite '
+ 'window. Nothing is materialized until `list()` consumes the '
+ 'iterator.'},
+ {'code': 'pages = itertools.chain(["intro", "setup"], ["deploy"])\n'
+ 'print(list(pages))',
+ 'prose': '`chain()` presents several iterables as one stream. This avoids '
+ 'building an intermediate list just to loop over combined inputs.'},
+ {'code': 'scores = [7, 10, 8, 10]\n'
+ 'high_scores = itertools.compress(scores, [score >= 9 for score in '
+ 'scores])\n'
+ 'print(list(high_scores))',
+ 'prose': 'Iterator helpers compose with ordinary Python expressions. '
+ '`compress()` keeps items whose corresponding selector is true.'}]},
+ {'cells': [{'code': 'from functools import wraps\n'
'\n'
'\n'
'def loud(func):\n'
@@ -2321,53 +4622,102 @@
'\n'
'manual_greet = loud(greet)\n'
'print(manual_greet("python"))',
+ 'kind': 'cell',
+ 'line': 23,
'output': 'HELLO PYTHON',
- 'line': 22,
- 'kind': 'cell'},
- {'prose': ['The `@loud` syntax performs the same rebinding at definition time. After decoration, `welcome` '
- 'refers to the wrapper returned by `loud`.'],
- 'code': '@loud\n'
+ 'prose': ['A decorator is just a function that takes a function and returns another '
+ 'callable. Applying it manually shows the wrapping step.']},
+ {'code': '@loud\n'
'def welcome(name):\n'
' """Return a welcome message."""\n'
' return f"welcome {name}"\n'
'\n'
'print(welcome("workers"))',
+ 'kind': 'cell',
+ 'line': 49,
'output': 'WELCOME WORKERS',
- 'line': 48,
- 'kind': 'cell'},
- {'prose': ['`functools.wraps` copies useful metadata from the original function onto the wrapper.'],
- 'code': 'print(welcome.__name__)\nprint(welcome.__doc__)',
+ 'prose': ['The `@loud` syntax performs the same rebinding at definition time. After '
+ 'decoration, `welcome` refers to the wrapper returned by `loud`.']},
+ {'code': 'print(welcome.__name__)\nprint(welcome.__doc__)',
+ 'kind': 'cell',
+ 'line': 66,
'output': 'welcome\nReturn a welcome message.',
- 'line': 65,
- 'kind': 'cell'}]},
- {'slug': 'classes',
- 'title': 'Classes',
- 'section': 'Classes',
- 'summary': 'Classes bundle data and behavior into new object types.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/classes.html',
- 'code': 'class Counter:\n'
- ' def __init__(self, start=0):\n'
- ' self.value = start\n'
+ 'prose': ['`functools.wraps` copies useful metadata from the original function onto '
+ 'the wrapper.']}],
+ 'code': 'from functools import wraps\n'
'\n'
- ' def increment(self, amount=1):\n'
- ' self.value += amount\n'
- ' return self.value\n'
'\n'
- 'first = Counter()\n'
- 'second = Counter(10)\n'
+ 'def loud(func):\n'
+ ' @wraps(func)\n'
+ ' def wrapper(name):\n'
+ ' return func(name).upper()\n'
+ ' return wrapper\n'
'\n'
- 'print(first.value)\n'
- 'print(second.value)\n'
- 'print(first.increment())\n'
- 'print(second.increment(5))\n',
- 'expected_output': '0\n10\n1\n15\n',
- 'notes': ['`self` is the instance the method is operating on.',
- '`__init__` initializes each new object.',
- 'Use classes when behavior belongs with state; use dictionaries for looser structured data.',
- 'Instance attributes belong to one object, not to the class as a whole.'],
- 'cells': [{'prose': ['Define a class when data and behavior should travel together. The initializer gives each '
- 'object its starting state.'],
- 'code': 'class Counter:\n'
+ '\n'
+ 'def greet(name):\n'
+ ' return f"hello {name}"\n'
+ '\n'
+ 'manual_greet = loud(greet)\n'
+ 'print(manual_greet("python"))\n'
+ '\n'
+ '@loud\n'
+ 'def welcome(name):\n'
+ ' """Return a welcome message."""\n'
+ ' return f"welcome {name}"\n'
+ '\n'
+ 'print(welcome("workers"))\n'
+ 'print(welcome.__name__)\n',
+ 'doc_path': '/glossary.html#term-decorator',
+ 'doc_url': 'https://docs.python.org/3.13/glossary.html#term-decorator',
+ 'expected_output': 'HELLO PYTHON\nWELCOME WORKERS\nwelcome\n',
+ 'explanation': ['A decorator is a callable that receives a function and returns a replacement. '
+ 'The `@` syntax applies that transformation at function definition time.',
+ 'Decorators are common in frameworks because they can register handlers or add '
+ 'behavior while keeping the decorated function focused on the core action.',
+ "`@decorator` is shorthand for rebinding a function to the decorator's return "
+ 'value. Production wrappers usually use `functools.wraps` so debugging, help '
+ 'text, and framework introspection still see the original function metadata.'],
+ 'min_python': None,
+ 'notes': ['`@decorator` is shorthand for assigning `func = decorator(func)`.',
+ 'Decorators can wrap, replace, or register functions.',
+ 'Use `functools.wraps` in production wrappers that should preserve metadata.'],
+ 'section': 'Functions',
+ 'see_also': ['closures', 'functions', 'callable-types', 'classmethods-and-staticmethods'],
+ 'slug': 'decorators',
+ 'summary': 'Decorators wrap or register functions using @ syntax.',
+ 'title': 'Decorators',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'from functools import wraps\n'
+ '\n'
+ '\n'
+ 'def loud(func):\n'
+ ' @wraps(func)\n'
+ ' def wrapper(name):\n'
+ ' return func(name).upper()\n'
+ ' return wrapper\n'
+ '\n'
+ '\n'
+ 'def greet(name):\n'
+ ' return f"hello {name}"\n'
+ '\n'
+ 'manual_greet = loud(greet)\n'
+ 'print(manual_greet("python"))',
+ 'prose': 'A decorator is just a function that takes a function and returns '
+ 'another callable. Applying it manually shows the wrapping step.'},
+ {'code': '@loud\n'
+ 'def welcome(name):\n'
+ ' """Return a welcome message."""\n'
+ ' return f"welcome {name}"\n'
+ '\n'
+ 'print(welcome("workers"))',
+ 'prose': 'The `@loud` syntax performs the same rebinding at definition time. '
+ 'After decoration, `welcome` refers to the wrapper returned by '
+ '`loud`.'},
+ {'code': 'print(welcome.__name__)\nprint(welcome.__doc__)',
+ 'prose': '`functools.wraps` copies useful metadata from the original function '
+ 'onto the wrapper.'}]},
+ {'cells': [{'code': 'class Counter:\n'
' def __init__(self, start=0):\n'
' self.value = start\n'
'\n'
@@ -2375,12 +4725,12 @@
'second = Counter(10)\n'
'print(first.value)\n'
'print(second.value)',
+ 'kind': 'cell',
+ 'line': 23,
'output': '0\n10',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['Methods are functions attached to the class. `self` is the particular object receiving the '
- 'method call, so separate instances keep separate state.'],
- 'code': 'class Counter:\n'
+ 'prose': ['Define a class when data and behavior should travel together. The '
+ 'initializer gives each object its starting state.']},
+ {'code': 'class Counter:\n'
' def __init__(self, start=0):\n'
' self.value = start\n'
'\n'
@@ -2392,14 +4742,222 @@
'second = Counter(10)\n'
'print(first.increment())\n'
'print(second.increment(5))',
+ 'kind': 'cell',
+ 'line': 43,
'output': '1\n15',
- 'line': 37,
- 'kind': 'cell'}]},
- {'slug': 'inheritance-and-super',
- 'title': 'Inheritance and Super',
+ 'prose': ['Methods are functions attached to the class. `self` is the particular '
+ 'object receiving the method call, so separate instances keep separate '
+ 'state.']},
+ {'code': 'class Counter:\n'
+ ' step = 1\n'
+ '\n'
+ ' def __init__(self, start=0):\n'
+ ' self.value = start\n'
+ '\n'
+ 'first = Counter()\n'
+ 'second = Counter()\n'
+ 'print(first.step)\n'
+ 'Counter.step = 5\n'
+ 'print(second.step)',
+ 'kind': 'cell',
+ 'line': 67,
+ 'output': '1\n5',
+ 'prose': ['A name defined directly on the class body is a class attribute, shared by '
+ 'every instance. Reading falls back to the class when the instance has no '
+ 'attribute of that name; assigning to the class itself changes the value '
+ 'for every instance at once.']},
+ {'code': 'class Cart:\n'
+ ' items = []\n'
+ '\n'
+ ' def add(self, item):\n'
+ ' self.items.append(item)\n'
+ '\n'
+ 'shared_a = Cart()\n'
+ 'shared_b = Cart()\n'
+ 'shared_a.add("apple")\n'
+ 'print(shared_b.items)\n'
+ '\n'
+ 'class FixedCart:\n'
+ ' def __init__(self):\n'
+ ' self.items = []\n'
+ '\n'
+ ' def add(self, item):\n'
+ ' self.items.append(item)\n'
+ '\n'
+ 'own_a = FixedCart()\n'
+ 'own_b = FixedCart()\n'
+ 'own_a.add("apple")\n'
+ 'print(own_b.items)',
+ 'kind': 'cell',
+ 'line': 90,
+ 'output': "['apple']\n[]",
+ 'prose': ['A mutable class attribute is shared mutable state — the classic footgun. '
+ 'Define per-instance containers in `__init__` so each object owns its own '
+ 'copy.']}],
+ 'code': 'class Counter:\n'
+ ' step = 1\n'
+ '\n'
+ ' def __init__(self, start=0):\n'
+ ' self.value = start\n'
+ '\n'
+ ' def increment(self, amount=1):\n'
+ ' self.value += amount\n'
+ ' return self.value\n'
+ '\n'
+ 'first = Counter()\n'
+ 'second = Counter(10)\n'
+ '\n'
+ 'print(first.value)\n'
+ 'print(second.value)\n'
+ 'print(first.increment())\n'
+ 'print(second.increment(5))\n'
+ 'print(first.step)\n'
+ 'Counter.step = 5\n'
+ 'print(second.step)\n'
+ '\n'
+ 'class Cart:\n'
+ ' items = []\n'
+ '\n'
+ ' def add(self, item):\n'
+ ' self.items.append(item)\n'
+ '\n'
+ 'shared_a = Cart()\n'
+ 'shared_b = Cart()\n'
+ 'shared_a.add("apple")\n'
+ 'print(shared_b.items)\n'
+ '\n'
+ 'class FixedCart:\n'
+ ' def __init__(self):\n'
+ ' self.items = []\n'
+ '\n'
+ ' def add(self, item):\n'
+ ' self.items.append(item)\n'
+ '\n'
+ 'own_a = FixedCart()\n'
+ 'own_b = FixedCart()\n'
+ 'own_a.add("apple")\n'
+ 'print(own_b.items)\n',
+ 'doc_path': '/tutorial/classes.html',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/classes.html',
+ 'expected_output': "0\n10\n1\n15\n1\n5\n['apple']\n[]\n",
+ 'explanation': ['Classes define new object types by bundling data with behavior. They are useful '
+ 'when several values and operations belong together and should travel as one '
+ 'object.',
+ 'The alternative is often a dictionary plus separate functions. That is fine for '
+ 'loose data, but a class gives the data a stable API and keeps behavior next to '
+ 'the state it changes.',
+ '`__init__` initializes each instance, and methods receive the instance as '
+ '`self`. Separate instances keep separate state because each object has its own '
+ 'attributes.'],
+ 'min_python': None,
+ 'notes': ['`self` is the instance the method is operating on.',
+ '`__init__` initializes each new object.',
+ 'Class attributes are shared across instances; instance attributes belong to one '
+ 'object.',
+ 'Put mutable defaults in `__init__`, not on the class body.',
+ 'Use classes when behavior belongs with state; use dictionaries for looser structured '
+ 'data.'],
'section': 'Classes',
- 'summary': 'Inheritance reuses behavior, and super delegates to a parent implementation.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/classes.html#inheritance',
+ 'see_also': ['inheritance-and-super',
+ 'classmethods-and-staticmethods',
+ 'bound-and-unbound-methods',
+ 'dataclasses'],
+ 'slug': 'classes',
+ 'summary': 'Classes bundle data and behavior into new object types.',
+ 'title': 'Classes',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'class Counter:\n'
+ ' def __init__(self, start=0):\n'
+ ' self.value = start\n'
+ '\n'
+ 'first = Counter()\n'
+ 'second = Counter(10)\n'
+ 'print(first.value)\n'
+ 'print(second.value)',
+ 'prose': 'Define a class when data and behavior should travel together. The '
+ 'initializer gives each object its starting state.'},
+ {'code': 'class Counter:\n'
+ ' def __init__(self, start=0):\n'
+ ' self.value = start\n'
+ '\n'
+ ' def increment(self, amount=1):\n'
+ ' self.value += amount\n'
+ ' return self.value\n'
+ '\n'
+ 'first = Counter()\n'
+ 'second = Counter(10)\n'
+ 'print(first.increment())\n'
+ 'print(second.increment(5))',
+ 'prose': 'Methods are functions attached to the class. `self` is the particular '
+ 'object receiving the method call, so separate instances keep separate '
+ 'state.'},
+ {'code': 'class Counter:\n'
+ ' step = 1\n'
+ '\n'
+ ' def __init__(self, start=0):\n'
+ ' self.value = start\n'
+ '\n'
+ 'first = Counter()\n'
+ 'second = Counter()\n'
+ 'print(first.step)\n'
+ 'Counter.step = 5\n'
+ 'print(second.step)',
+ 'prose': 'A name defined directly on the class body is a class attribute, '
+ 'shared by every instance. Reading falls back to the class when the '
+ 'instance has no attribute of that name; assigning to the class itself '
+ 'changes the value for every instance at once.'},
+ {'code': 'class Cart:\n'
+ ' items = []\n'
+ '\n'
+ ' def add(self, item):\n'
+ ' self.items.append(item)\n'
+ '\n'
+ 'shared_a = Cart()\n'
+ 'shared_b = Cart()\n'
+ 'shared_a.add("apple")\n'
+ 'print(shared_b.items)\n'
+ '\n'
+ 'class FixedCart:\n'
+ ' def __init__(self):\n'
+ ' self.items = []\n'
+ '\n'
+ ' def add(self, item):\n'
+ ' self.items.append(item)\n'
+ '\n'
+ 'own_a = FixedCart()\n'
+ 'own_b = FixedCart()\n'
+ 'own_a.add("apple")\n'
+ 'print(own_b.items)',
+ 'prose': 'A mutable class attribute is shared mutable state — the classic '
+ 'footgun. Define per-instance containers in `__init__` so each object '
+ 'owns its own copy.'}]},
+ {'cells': [{'code': 'class Animal:\n'
+ ' def __init__(self, name):\n'
+ ' self.name = name\n'
+ '\n'
+ ' def speak(self):\n'
+ ' return f"{self.name} makes a sound"\n'
+ '\n'
+ 'class Dog(Animal):\n'
+ ' def speak(self):\n'
+ ' base = super().speak()\n'
+ ' return f"{base}; {self.name} barks"\n'
+ '\n'
+ 'pet = Dog("Nina")\n'
+ 'print(pet.name)',
+ 'kind': 'cell',
+ 'line': 23,
+ 'output': 'Nina',
+ 'prose': ['A child class names its parent in parentheses. `Dog` instances get the '
+ '`Animal.__init__` method because `Dog` does not define its own '
+ 'initializer.']},
+ {'code': 'print(pet.speak())\nprint(isinstance(pet, Animal))',
+ 'kind': 'cell',
+ 'line': 48,
+ 'output': 'Nina makes a sound; Nina barks\nTrue',
+ 'prose': ['`super()` delegates to the parent implementation. The child method can '
+ 'reuse the parent result and then add specialized behavior.']}],
'code': 'class Animal:\n'
' def __init__(self, name):\n'
' self.name = name\n'
@@ -2416,40 +4974,281 @@
'print(pet.name)\n'
'print(pet.speak())\n'
'print(isinstance(pet, Animal))\n',
+ 'doc_path': '/tutorial/classes.html#inheritance',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/classes.html#inheritance',
'expected_output': 'Nina\nNina makes a sound; Nina barks\nTrue\n',
+ 'explanation': ['Inheritance lets one class specialize another class. The child class gets '
+ 'parent behavior and can add or override methods.',
+ 'Use `super()` when the child method should extend the parent implementation '
+ 'instead of replacing it entirely.',
+ 'Prefer composition when objects merely collaborate. Inheritance is best when '
+ 'the child really is a specialized version of the parent.'],
+ 'min_python': None,
'notes': ['Inheritance models an “is a specialized kind of” relationship.',
'`super()` calls the next implementation in the method resolution order.',
'Prefer composition when an object only needs to use another object.'],
- 'cells': [{'prose': ['A child class names its parent in parentheses. `Dog` instances get the `Animal.__init__` '
- 'method because `Dog` does not define its own initializer.'],
- 'code': 'class Animal:\n'
- ' def __init__(self, name):\n'
- ' self.name = name\n'
- '\n'
- ' def speak(self):\n'
- ' return f"{self.name} makes a sound"\n'
+ 'section': 'Classes',
+ 'see_also': ['classes',
+ 'abstract-base-classes',
+ 'classmethods-and-staticmethods',
+ 'special-methods'],
+ 'slug': 'inheritance-and-super',
+ 'summary': 'Inheritance reuses behavior, and super delegates to a parent implementation.',
+ 'title': 'Inheritance and Super',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'class Animal:\n'
+ ' def __init__(self, name):\n'
+ ' self.name = name\n'
+ '\n'
+ ' def speak(self):\n'
+ ' return f"{self.name} makes a sound"\n'
+ '\n'
+ 'class Dog(Animal):\n'
+ ' def speak(self):\n'
+ ' base = super().speak()\n'
+ ' return f"{base}; {self.name} barks"\n'
+ '\n'
+ 'pet = Dog("Nina")\n'
+ 'print(pet.name)',
+ 'prose': 'A child class names its parent in parentheses. `Dog` instances get '
+ 'the `Animal.__init__` method because `Dog` does not define its own '
+ 'initializer.'},
+ {'code': 'print(pet.speak())\nprint(isinstance(pet, Animal))',
+ 'prose': '`super()` delegates to the parent implementation. The child method '
+ 'can reuse the parent result and then add specialized behavior.'}]},
+ {'cells': [{'code': 'class Date:\n'
+ ' def __init__(self, year, month, day):\n'
+ ' self.year = year\n'
+ ' self.month = month\n'
+ ' self.day = day\n'
+ '\n'
+ ' def display(self):\n'
+ ' return f"{self.year}-{self.month:02d}-{self.day:02d}"\n'
+ '\n'
+ 'today = Date(2026, 5, 9)\n'
+ 'print(today.display())',
+ 'kind': 'cell',
+ 'line': 22,
+ 'output': '2026-05-09',
+ 'prose': ['An instance method receives the instance as `self` and reads its state. '
+ 'This is the default and the right shape when the work depends on a '
+ "particular object's data."]},
+ {'code': 'class Date:\n'
+ ' def __init__(self, year, month, day):\n'
+ ' self.year = year\n'
+ ' self.month = month\n'
+ ' self.day = day\n'
+ '\n'
+ ' @classmethod\n'
+ ' def from_string(cls, text):\n'
+ ' year, month, day = (int(part) for part in text.split("-"))\n'
+ ' return cls(year, month, day)\n'
+ '\n'
+ 'later = Date.from_string("2026-12-31")\n'
+ 'print(later.year, later.month, later.day)',
+ 'kind': 'cell',
+ 'line': 44,
+ 'output': '2026 12 31',
+ 'prose': ['`@classmethod` makes the method receive the class itself as `cls`. The '
+ 'canonical use is an alternate constructor that parses some other input '
+ 'format and calls `cls(...)`. Because `cls` is the actual class, subclasses '
+ 'calling the same method get an instance of their own type.']},
+ {'code': 'class Date:\n'
+ ' @staticmethod\n'
+ ' def is_leap_year(year):\n'
+ ' return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)\n'
+ '\n'
+ 'print(Date.is_leap_year(2024))\n'
+ 'print(Date.is_leap_year(2025))',
+ 'kind': 'cell',
+ 'line': 68,
+ 'output': 'True\nFalse',
+ 'prose': ['`@staticmethod` strips the implicit first argument. The function lives on '
+ 'the class for namespacing — like `Date.is_leap_year(2024)` — but does not '
+ 'touch any instance or class state.']},
+ {'code': 'class Demo:\n'
+ ' def instance_method(self):\n'
+ ' return type(self).__name__\n'
+ '\n'
+ ' @classmethod\n'
+ ' def class_method(cls):\n'
+ ' return cls.__name__\n'
+ '\n'
+ ' @staticmethod\n'
+ ' def static_method():\n'
+ ' return "no receiver"\n'
+ '\n'
+ 'print(Demo().instance_method())\n'
+ 'print(Demo.class_method())\n'
+ 'print(Demo.static_method())',
+ 'kind': 'cell',
+ 'line': 87,
+ 'output': 'Demo\nDemo\nno receiver',
+ 'prose': ['Side by side: instance methods receive the instance, classmethods receive '
+ 'the class, staticmethods receive nothing. Classmethods and staticmethods '
+ 'can be called on either the class or an instance.']}],
+ 'code': 'class Date:\n'
+ ' def __init__(self, year, month, day):\n'
+ ' self.year = year\n'
+ ' self.month = month\n'
+ ' self.day = day\n'
+ '\n'
+ ' def display(self):\n'
+ ' return f"{self.year}-{self.month:02d}-{self.day:02d}"\n'
+ '\n'
+ ' @classmethod\n'
+ ' def from_string(cls, text):\n'
+ ' year, month, day = (int(part) for part in text.split("-"))\n'
+ ' return cls(year, month, day)\n'
+ '\n'
+ ' @staticmethod\n'
+ ' def is_leap_year(year):\n'
+ ' return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)\n'
+ '\n'
+ 'today = Date(2026, 5, 9)\n'
+ 'print(today.display())\n'
+ '\n'
+ 'later = Date.from_string("2026-12-31")\n'
+ 'print(later.display())\n'
+ '\n'
+ 'print(Date.is_leap_year(2024))\n'
+ 'print(Date.is_leap_year(2025))\n'
+ '\n'
+ 'class Demo:\n'
+ ' def instance_method(self):\n'
+ ' return type(self).__name__\n'
+ '\n'
+ ' @classmethod\n'
+ ' def class_method(cls):\n'
+ ' return cls.__name__\n'
+ '\n'
+ ' @staticmethod\n'
+ ' def static_method():\n'
+ ' return "no receiver"\n'
+ '\n'
+ 'print(Demo().instance_method())\n'
+ 'print(Demo.class_method())\n'
+ 'print(Demo.static_method())\n',
+ 'doc_path': '/library/functions.html#classmethod',
+ 'doc_url': 'https://docs.python.org/3.13/library/functions.html#classmethod',
+ 'expected_output': '2026-05-09\n2026-12-31\nTrue\nFalse\nDemo\nDemo\nno receiver\n',
+ 'explanation': ['A regular method receives the instance as `self`. `@classmethod` makes a method '
+ 'receive the class as `cls` instead, which is the standard shape for alternate '
+ 'constructors. `@staticmethod` removes the implicit first argument entirely, '
+ 'leaving a plain function attached to the class for namespacing.',
+ 'The pressure that justifies the decorators is name organization. '
+ '`Date.from_string("2026-05-09")` reads better than a free-floating `parse_date` '
+ 'function, and `Date.is_leap_year(2024)` keeps the helper next to the class it '
+ 'belongs to even when the helper does not need any class state.',
+ 'Pick instance methods when the work depends on instance state, classmethods '
+ 'when an alternate constructor or class-level operation is the right shape, and '
+ 'staticmethods when the function only happens to live near a class.'],
+ 'min_python': None,
+ 'notes': ['Instance methods need an instance; classmethods and staticmethods can be called on '
+ 'the class.',
+ 'Use `@classmethod` for alternate constructors and class-level operations that respect '
+ 'subclassing.',
+ 'Use `@staticmethod` only when a function is truly independent of instance and class '
+ "state but still belongs in the class's namespace.",
+ 'A free function is often the right answer when neither decorator applies.'],
+ 'section': 'Classes',
+ 'see_also': ['classes', 'decorators', 'inheritance-and-super'],
+ 'slug': 'classmethods-and-staticmethods',
+ 'summary': 'Three method shapes: instance, class, and static — each receives a different first '
+ 'argument.',
+ 'title': 'Classmethods and Staticmethods',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'class Date:\n'
+ ' def __init__(self, year, month, day):\n'
+ ' self.year = year\n'
+ ' self.month = month\n'
+ ' self.day = day\n'
+ '\n'
+ ' def display(self):\n'
+ ' return f"{self.year}-{self.month:02d}-{self.day:02d}"\n'
+ '\n'
+ 'today = Date(2026, 5, 9)\n'
+ 'print(today.display())',
+ 'prose': 'An instance method receives the instance as `self` and reads its '
+ 'state. This is the default and the right shape when the work depends '
+ "on a particular object's data."},
+ {'code': 'class Date:\n'
+ ' def __init__(self, year, month, day):\n'
+ ' self.year = year\n'
+ ' self.month = month\n'
+ ' self.day = day\n'
+ '\n'
+ ' @classmethod\n'
+ ' def from_string(cls, text):\n'
+ ' year, month, day = (int(part) for part in text.split("-"))\n'
+ ' return cls(year, month, day)\n'
+ '\n'
+ 'later = Date.from_string("2026-12-31")\n'
+ 'print(later.year, later.month, later.day)',
+ 'prose': '`@classmethod` makes the method receive the class itself as `cls`. '
+ 'The canonical use is an alternate constructor that parses some other '
+ 'input format and calls `cls(...)`. Because `cls` is the actual class, '
+ 'subclasses calling the same method get an instance of their own '
+ 'type.'},
+ {'code': 'class Date:\n'
+ ' @staticmethod\n'
+ ' def is_leap_year(year):\n'
+ ' return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)\n'
+ '\n'
+ 'print(Date.is_leap_year(2024))\n'
+ 'print(Date.is_leap_year(2025))',
+ 'prose': '`@staticmethod` strips the implicit first argument. The function '
+ 'lives on the class for namespacing — like `Date.is_leap_year(2024)` — '
+ 'but does not touch any instance or class state.'},
+ {'code': 'class Demo:\n'
+ ' def instance_method(self):\n'
+ ' return type(self).__name__\n'
+ '\n'
+ ' @classmethod\n'
+ ' def class_method(cls):\n'
+ ' return cls.__name__\n'
+ '\n'
+ ' @staticmethod\n'
+ ' def static_method():\n'
+ ' return "no receiver"\n'
+ '\n'
+ 'print(Demo().instance_method())\n'
+ 'print(Demo.class_method())\n'
+ 'print(Demo.static_method())',
+ 'prose': 'Side by side: instance methods receive the instance, classmethods '
+ 'receive the class, staticmethods receive nothing. Classmethods and '
+ 'staticmethods can be called on either the class or an instance.'}]},
+ {'cells': [{'code': 'from dataclasses import dataclass\n'
'\n'
- 'class Dog(Animal):\n'
- ' def speak(self):\n'
- ' base = super().speak()\n'
- ' return f"{base}; {self.name} barks"\n'
+ '@dataclass\n'
+ 'class User:\n'
+ ' name: str\n'
+ ' active: bool = True\n'
'\n'
- 'pet = Dog("Nina")\n'
- 'print(pet.name)',
- 'output': 'Nina',
+ 'user = User("Ada")\n'
+ 'print(user)',
+ 'kind': 'cell',
'line': 22,
- 'kind': 'cell'},
- {'prose': ['`super()` delegates to the parent implementation. The child method can reuse the parent result '
- 'and then add specialized behavior.'],
- 'code': 'print(pet.speak())\nprint(isinstance(pet, Animal))',
- 'output': 'Nina makes a sound; Nina barks\nTrue',
- 'line': 47,
- 'kind': 'cell'}]},
- {'slug': 'dataclasses',
- 'title': 'Dataclasses',
- 'section': 'Classes',
- 'summary': 'dataclass generates common class methods for data containers.',
- 'doc_url': 'https://docs.python.org/3.13/library/dataclasses.html',
+ 'output': "User(name='Ada', active=True)",
+ 'prose': ['A dataclass uses annotations to define fields. Python generates an '
+ 'initializer, so the class can be constructed without writing `__init__` by '
+ 'hand.']},
+ {'code': 'print(user.name)',
+ 'kind': 'cell',
+ 'line': 42,
+ 'output': 'Ada',
+ 'prose': ['The generated instance still exposes ordinary attributes. A dataclass is a '
+ 'regular class with useful methods filled in.']},
+ {'code': 'inactive = User("Guido", active=False)\n'
+ 'print(inactive)\n'
+ 'print(inactive.active)',
+ 'kind': 'cell',
+ 'line': 54,
+ 'output': "User(name='Guido', active=False)\nFalse",
+ 'prose': ['Defaults can be overridden by keyword. The generated representation '
+ 'includes the field names, which is useful during debugging.']}],
'code': 'from dataclasses import dataclass\n'
'\n'
'@dataclass\n'
@@ -2464,41 +5263,90 @@
'inactive = User("Guido", active=False)\n'
'print(inactive)\n'
'print(inactive.active)\n',
- 'expected_output': "User(name='Ada', active=True)\nAda\nUser(name='Guido', active=False)\nFalse\n",
+ 'doc_path': '/library/dataclasses.html',
+ 'doc_url': 'https://docs.python.org/3.13/library/dataclasses.html',
+ 'expected_output': "User(name='Ada', active=True)\n"
+ 'Ada\n'
+ "User(name='Guido', active=False)\n"
+ 'False\n',
+ 'explanation': ['`dataclass` is a standard-library decorator for classes that mainly store data. '
+ 'It generates methods such as `__init__` and `__repr__` from type-annotated '
+ 'fields.',
+ 'Dataclasses reduce boilerplate while keeping classes explicit. They are a good '
+ 'fit for simple records, configuration objects, and values passed between '
+ 'layers.',
+ 'Type annotations define fields. Defaults work like normal class attributes and '
+ 'appear in the generated initializer.'],
+ 'min_python': None,
'notes': ['Type annotations define dataclass fields.',
'Dataclasses generate methods but remain normal Python classes.',
'Use `field()` for advanced defaults such as per-instance lists or dictionaries.'],
- 'cells': [{'prose': ['A dataclass uses annotations to define fields. Python generates an initializer, so the class '
- 'can be constructed without writing `__init__` by hand.'],
- 'code': 'from dataclasses import dataclass\n'
+ 'section': 'Classes',
+ 'see_also': ['structured-data-shapes', 'classes', 'type-hints'],
+ 'slug': 'dataclasses',
+ 'summary': 'dataclass generates common class methods for data containers.',
+ 'title': 'Dataclasses',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'from dataclasses import dataclass\n'
+ '\n'
+ '@dataclass\n'
+ 'class User:\n'
+ ' name: str\n'
+ ' active: bool = True\n'
+ '\n'
+ 'user = User("Ada")\n'
+ 'print(user)',
+ 'prose': 'A dataclass uses annotations to define fields. Python generates an '
+ 'initializer, so the class can be constructed without writing '
+ '`__init__` by hand.'},
+ {'code': 'print(user.name)',
+ 'prose': 'The generated instance still exposes ordinary attributes. A dataclass '
+ 'is a regular class with useful methods filled in.'},
+ {'code': 'inactive = User("Guido", active=False)\n'
+ 'print(inactive)\n'
+ 'print(inactive.active)',
+ 'prose': 'Defaults can be overridden by keyword. The generated representation '
+ 'includes the field names, which is useful during debugging.'}]},
+ {'cells': [{'code': 'class Rectangle:\n'
+ ' def __init__(self, width, height):\n'
+ ' self.width = width\n'
+ ' self.height = height\n'
'\n'
- '@dataclass\n'
- 'class User:\n'
- ' name: str\n'
- ' active: bool = True\n'
+ ' @property\n'
+ ' def area(self):\n'
+ ' return self.width * self.height\n'
'\n'
- 'user = User("Ada")\n'
- 'print(user)',
- 'output': "User(name='Ada', active=True)",
+ ' @property\n'
+ ' def width(self):\n'
+ ' return self._width\n'
+ '\n'
+ ' @width.setter\n'
+ ' def width(self, value):\n'
+ ' if value <= 0:\n'
+ ' raise ValueError("width must be positive")\n'
+ ' self._width = value\n'
+ '\n'
+ 'box = Rectangle(3, 4)\n'
+ 'print(box.area)',
+ 'kind': 'cell',
'line': 17,
- 'kind': 'cell'},
- {'prose': ['The generated instance still exposes ordinary attributes. A dataclass is a regular class with '
- 'useful methods filled in.'],
- 'code': 'print(user.name)',
- 'output': 'Ada',
- 'line': 37,
- 'kind': 'cell'},
- {'prose': ['Defaults can be overridden by keyword. The generated representation includes the field names, '
- 'which is useful during debugging.'],
- 'code': 'inactive = User("Guido", active=False)\nprint(inactive)\nprint(inactive.active)',
- 'output': "User(name='Guido', active=False)\nFalse",
+ 'output': '12',
+ 'prose': ['A read-only property exposes computed data through attribute access. '
+ '`area` stays current because it is calculated from `width` and `height` '
+ 'each time it is read.']},
+ {'code': 'box.width = 5\nprint(box.area)',
+ 'kind': 'cell',
'line': 49,
- 'kind': 'cell'}]},
- {'slug': 'properties',
- 'title': 'Properties',
- 'section': 'Classes',
- 'summary': '@property keeps attribute syntax while adding computation or validation.',
- 'doc_url': 'https://docs.python.org/3.13/library/functions.html#property',
+ 'output': '20',
+ 'prose': ['A setter lets assignment keep normal attribute syntax while the class '
+ 'validates or normalizes the value.']},
+ {'code': 'try:\n box.width = 0\nexcept ValueError as error:\n print(error)',
+ 'kind': 'cell',
+ 'line': 62,
+ 'output': 'width must be positive',
+ 'prose': ['Validation belongs inside the class when every caller should obey the same '
+ 'rule. Invalid assignment raises an exception at the boundary.']}],
'code': 'class Rectangle:\n'
' def __init__(self, width, height):\n'
' self.width = width\n'
@@ -2528,87 +5376,70 @@
' box.width = 0\n'
'except ValueError as error:\n'
' print(error)\n',
+ 'doc_path': '/library/functions.html#property',
+ 'doc_url': 'https://docs.python.org/3.13/library/functions.html#property',
'expected_output': '12\n20\nwidth must be positive\n',
+ 'explanation': ['Properties let a class keep a simple attribute-style API while running code '
+ 'behind the scenes. Callers write `box.area`, but the class can compute the '
+ 'value from current state.',
+ 'A property setter can validate assignment without changing the public spelling '
+ 'of the attribute. This is the boundary: plain attributes are enough for plain '
+ 'data, while properties are for computed or protected data.',
+ 'Use properties for cheap, attribute-like operations. Expensive work or actions '
+ 'with side effects should usually remain explicit methods.'],
+ 'min_python': None,
'notes': ['Properties let APIs start simple and grow validation or computation later.',
'Callers access a property like an attribute, not like a method.',
'Use methods instead when work is expensive or action-like.'],
- 'cells': [{'prose': ['A read-only property exposes computed data through attribute access. `area` stays current '
- 'because it is calculated from `width` and `height` each time it is read.'],
- 'code': 'class Rectangle:\n'
- ' def __init__(self, width, height):\n'
- ' self.width = width\n'
- ' self.height = height\n'
- '\n'
- ' @property\n'
- ' def area(self):\n'
- ' return self.width * self.height\n'
- '\n'
- ' @property\n'
- ' def width(self):\n'
- ' return self._width\n'
- '\n'
- ' @width.setter\n'
- ' def width(self, value):\n'
- ' if value <= 0:\n'
- ' raise ValueError("width must be positive")\n'
- ' self._width = value\n'
- '\n'
- 'box = Rectangle(3, 4)\n'
- 'print(box.area)',
- 'output': '12',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['A setter lets assignment keep normal attribute syntax while the class validates or normalizes '
- 'the value.'],
- 'code': 'box.width = 5\nprint(box.area)',
- 'output': '20',
- 'line': 49,
- 'kind': 'cell'},
- {'prose': ['Validation belongs inside the class when every caller should obey the same rule. Invalid '
- 'assignment raises an exception at the boundary.'],
- 'code': 'try:\n box.width = 0\nexcept ValueError as error:\n print(error)',
- 'output': 'width must be positive',
- 'line': 62,
- 'kind': 'cell'}]},
- {'slug': 'special-methods',
- 'title': 'Special Methods',
- 'section': 'Data Model',
- 'summary': 'Special methods connect your objects to Python syntax and built-ins.',
- 'doc_url': 'https://docs.python.org/3.13/reference/datamodel.html#special-method-names',
- 'code': 'class Bag:\n'
- ' def __init__(self, items):\n'
- ' self.items = list(items)\n'
- '\n'
- ' def __len__(self):\n'
- ' return len(self.items)\n'
- '\n'
- ' def __iter__(self):\n'
- ' return iter(self.items)\n'
- '\n'
- ' def __repr__(self):\n'
- ' return f"Bag({self.items!r})"\n'
- '\n'
- 'bag = Bag(["a", "b"])\n'
- 'print(len(bag))\n'
- 'print(list(bag))\n'
- 'print(bag)\n',
- 'expected_output': "2\n['a', 'b']\nBag(['a', 'b'])\n",
- 'notes': ["Dunder methods are looked up by Python's data model protocols.",
- 'Implement the smallest protocol that makes your object feel native.'],
- 'cells': [{'prose': ['Start with a normal class that stores its data. Special methods build on ordinary instance '
- 'state.'],
- 'code': 'class Bag:\n'
+ 'section': 'Classes',
+ 'see_also': [],
+ 'slug': 'properties',
+ 'summary': '@property keeps attribute syntax while adding computation or validation.',
+ 'title': 'Properties',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'class Rectangle:\n'
+ ' def __init__(self, width, height):\n'
+ ' self.width = width\n'
+ ' self.height = height\n'
+ '\n'
+ ' @property\n'
+ ' def area(self):\n'
+ ' return self.width * self.height\n'
+ '\n'
+ ' @property\n'
+ ' def width(self):\n'
+ ' return self._width\n'
+ '\n'
+ ' @width.setter\n'
+ ' def width(self, value):\n'
+ ' if value <= 0:\n'
+ ' raise ValueError("width must be positive")\n'
+ ' self._width = value\n'
+ '\n'
+ 'box = Rectangle(3, 4)\n'
+ 'print(box.area)',
+ 'prose': 'A read-only property exposes computed data through attribute access. '
+ '`area` stays current because it is calculated from `width` and '
+ '`height` each time it is read.'},
+ {'code': 'box.width = 5\nprint(box.area)',
+ 'prose': 'A setter lets assignment keep normal attribute syntax while the class '
+ 'validates or normalizes the value.'},
+ {'code': 'try:\n box.width = 0\nexcept ValueError as error:\n print(error)',
+ 'prose': 'Validation belongs inside the class when every caller should obey the '
+ 'same rule. Invalid assignment raises an exception at the boundary.'}]},
+ {'cells': [{'code': 'class Bag:\n'
' def __init__(self, items):\n'
' self.items = list(items)\n'
'\n'
'bag = Bag(["a", "b"])\n'
'print(bag.items)',
+ 'kind': 'cell',
+ 'line': 23,
'output': "['a', 'b']",
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ["Implement `__len__` to let `len()` ask the object for its size using Python's standard "
- 'protocol.'],
- 'code': 'class Bag:\n'
+ 'prose': ['Start with a normal class that stores its data. Special methods build on '
+ 'ordinary instance state.']},
+ {'code': 'class Bag:\n'
' def __init__(self, items):\n'
' self.items = list(items)\n'
'\n'
@@ -2617,12 +5448,12 @@
'\n'
'bag = Bag(["a", "b"])\n'
'print(len(bag))',
+ 'kind': 'cell',
+ 'line': 40,
'output': '2',
- 'line': 34,
- 'kind': 'cell'},
- {'prose': ['Implement `__iter__` to make the object iterable. Then tools such as `list()` can consume it '
- 'without a custom method name.'],
- 'code': 'class Bag:\n'
+ 'prose': ['Implement `__len__` to let `len()` ask the object for its size using '
+ "Python's standard protocol."]},
+ {'code': 'class Bag:\n'
' def __init__(self, items):\n'
' self.items = list(items)\n'
'\n'
@@ -2634,12 +5465,12 @@
'\n'
'bag = Bag(["a", "b"])\n'
'print(list(bag))',
+ 'kind': 'cell',
+ 'line': 60,
'output': "['a', 'b']",
- 'line': 54,
- 'kind': 'cell'},
- {'prose': ['Implement `__repr__` to give the object a useful developer-facing representation when it is '
- 'printed or inspected.'],
- 'code': 'class Bag:\n'
+ 'prose': ['Implement `__iter__` to make the object iterable. Then tools such as '
+ '`list()` can consume it without a custom method name.']},
+ {'code': 'class Bag:\n'
' def __init__(self, items):\n'
' self.items = list(items)\n'
'\n'
@@ -2654,37 +5485,376 @@
'\n'
'bag = Bag(["a", "b"])\n'
'print(bag)',
+ 'kind': 'cell',
+ 'line': 83,
'output': "Bag(['a', 'b'])",
- 'line': 77,
- 'kind': 'cell'}]},
- {'slug': 'truth-and-size',
- 'title': 'Truth and Size',
- 'section': 'Data Model',
- 'summary': '__bool__ and __len__ decide how objects behave in truth tests and len().',
- 'doc_url': 'https://docs.python.org/3.13/reference/datamodel.html#object.__bool__',
- 'code': 'class Inbox:\n'
- ' def __init__(self, messages):\n'
- ' self.messages = list(messages)\n'
+ 'prose': ['Implement `__repr__` to give the object a useful developer-facing '
+ 'representation when it is printed or inspected. With no `__str__` defined, '
+ '`print()` falls back to `__repr__`.']},
+ {'code': 'class Bag:\n'
+ ' def __init__(self, items):\n'
+ ' self.items = list(items)\n'
+ '\n'
+ ' def __repr__(self):\n'
+ ' return f"Bag({self.items!r})"\n'
+ '\n'
+ ' def __str__(self):\n'
+ ' return ", ".join(self.items)\n'
+ '\n'
+ 'bag = Bag(["a", "b"])\n'
+ 'print(bag)\n'
+ 'print(repr(bag))',
+ 'kind': 'cell',
+ 'line': 109,
+ 'output': "a, b\nBag(['a', 'b'])",
+ 'prose': ['Add `__str__` for an end-user representation. `print()` and `str()` prefer '
+ '`__str__`; `repr()` and the REPL still use `__repr__`. Keep `__repr__` '
+ 'unambiguous for debugging and let `__str__` be the friendly form.']},
+ {'code': 'class Bag:\n'
+ ' def __init__(self, items):\n'
+ ' self.items = list(items)\n'
+ '\n'
+ ' def __eq__(self, other):\n'
+ ' return isinstance(other, Bag) and self.items == other.items\n'
+ '\n'
+ ' def __hash__(self):\n'
+ ' return hash(tuple(self.items))\n'
+ '\n'
+ ' def __lt__(self, other):\n'
+ ' return len(self.items) < len(other.items)\n'
+ '\n'
+ 'print(Bag(["a", "b"]) == Bag(["a", "b"]))\n'
+ 'print(Bag(["a"]) < Bag(["a", "b"]))\n'
+ 'print(hash(Bag(["a"])) == hash(Bag(["a"])))',
+ 'kind': 'cell',
+ 'line': 134,
+ 'output': 'True\nTrue\nTrue',
+ 'prose': ['`__eq__` decides what equality means for the type. Defining `__eq__` '
+ 'removes the default `__hash__`, so add `__hash__` back when instances '
+ 'should work in sets or as dict keys. `__lt__` enables `<` and, with the '
+ 'rest of the order family, `sorted()`.']},
+ {'code': 'class Bag:\n'
+ ' def __init__(self, items):\n'
+ ' self.items = list(items)\n'
+ '\n'
+ ' def __contains__(self, item):\n'
+ ' return item in self.items\n'
+ '\n'
+ ' def __getitem__(self, index):\n'
+ ' return self.items[index]\n'
+ '\n'
+ ' def __setitem__(self, index, value):\n'
+ ' self.items[index] = value\n'
+ '\n'
+ ' def __bool__(self):\n'
+ ' return bool(self.items)\n'
+ '\n'
+ 'bag = Bag(["a", "b"])\n'
+ 'print("a" in bag)\n'
+ 'print(bag[0])\n'
+ 'bag[1] = "z"\n'
+ 'print(bag.items)\n'
+ 'print(bool(Bag([])))',
+ 'kind': 'cell',
+ 'line': 163,
+ 'output': "True\na\n['a', 'z']\nFalse",
+ 'prose': ['The container protocols make instances behave like built-in containers. '
+ '`__contains__` powers `in`, `__getitem__`/`__setitem__` power '
+ 'subscription, and `__bool__` decides truthiness for `if` and `while`. See '
+ '[container-protocols](/data-model/container-protocols) for the full '
+ 'surface.']},
+ {'code': 'class Multiplier:\n'
+ ' def __init__(self, factor):\n'
+ ' self.factor = factor\n'
+ '\n'
+ ' def __call__(self, value):\n'
+ ' return value * self.factor\n'
+ '\n'
+ 'triple = Multiplier(3)\n'
+ 'print(triple(5))\n'
+ '\n'
+ '\n'
+ 'class Trace:\n'
+ ' def __enter__(self):\n'
+ ' print("enter")\n'
+ ' return self\n'
+ '\n'
+ ' def __exit__(self, *exc):\n'
+ ' print("exit")\n'
+ ' return False\n'
+ '\n'
+ 'with Trace():\n'
+ ' print("inside")',
+ 'kind': 'cell',
+ 'line': 199,
+ 'output': '15\nenter\ninside\nexit',
+ 'prose': ['`__call__` makes an instance callable like a function — useful for '
+ 'stateful operations whose configuration deserves a name. `__enter__` and '
+ '`__exit__` make a class a context manager so it can be used with `with`. '
+ 'The focused [callable-objects](/data-model/callable-objects) and '
+ '[context-managers](/data-model/context-managers) pages go deeper.']}],
+ 'code': 'class Bag:\n'
+ ' def __init__(self, items):\n'
+ ' self.items = list(items)\n'
'\n'
' def __len__(self):\n'
- ' return len(self.messages)\n'
+ ' return len(self.items)\n'
'\n'
- 'class Account:\n'
- ' def __init__(self, active):\n'
- ' self.active = active\n'
+ ' def __iter__(self):\n'
+ ' return iter(self.items)\n'
+ '\n'
+ ' def __repr__(self):\n'
+ ' return f"Bag({self.items!r})"\n'
+ '\n'
+ ' def __str__(self):\n'
+ ' return ", ".join(self.items)\n'
+ '\n'
+ ' def __eq__(self, other):\n'
+ ' return isinstance(other, Bag) and self.items == other.items\n'
+ '\n'
+ ' def __hash__(self):\n'
+ ' return hash(tuple(self.items))\n'
+ '\n'
+ ' def __lt__(self, other):\n'
+ ' return len(self.items) < len(other.items)\n'
+ '\n'
+ ' def __contains__(self, item):\n'
+ ' return item in self.items\n'
+ '\n'
+ ' def __getitem__(self, index):\n'
+ ' return self.items[index]\n'
+ '\n'
+ ' def __setitem__(self, index, value):\n'
+ ' self.items[index] = value\n'
'\n'
' def __bool__(self):\n'
- ' return self.active\n'
+ ' return bool(self.items)\n'
'\n'
- 'print(len(Inbox(["hi", "bye"])))\n'
- 'print(bool(Inbox([])))\n'
- 'print(bool(Account(False)))\n',
- 'expected_output': '2\nFalse\nFalse\n',
- 'notes': ['Prefer `__len__` for sized containers.',
- 'Prefer `__bool__` when truth has domain meaning.',
- 'Keep truth tests unsurprising; surprising falsy objects make conditionals harder to read.'],
- 'cells': [{'prose': ['`__len__` lets `len()` ask an object for its size.'],
- 'code': 'class Inbox:\n'
+ 'bag = Bag(["a", "b"])\n'
+ 'print(len(bag))\n'
+ 'print(list(bag))\n'
+ 'print(bag)\n'
+ 'print(repr(bag))\n'
+ 'print(Bag(["a", "b"]) == Bag(["a", "b"]))\n'
+ 'print(Bag(["a"]) < Bag(["a", "b"]))\n'
+ 'print(hash(Bag(["a"])) == hash(Bag(["a"])))\n'
+ 'print("a" in bag)\n'
+ 'print(bag[0])\n'
+ 'bag[1] = "z"\n'
+ 'print(list(bag))\n'
+ 'print(bool(Bag([])))\n'
+ '\n'
+ '\n'
+ 'class Multiplier:\n'
+ ' def __init__(self, factor):\n'
+ ' self.factor = factor\n'
+ '\n'
+ ' def __call__(self, value):\n'
+ ' return value * self.factor\n'
+ '\n'
+ 'triple = Multiplier(3)\n'
+ 'print(triple(5))\n'
+ '\n'
+ '\n'
+ 'class Trace:\n'
+ ' def __enter__(self):\n'
+ ' print("enter")\n'
+ ' return self\n'
+ '\n'
+ ' def __exit__(self, *exc):\n'
+ ' print("exit")\n'
+ ' return False\n'
+ '\n'
+ 'with Trace():\n'
+ ' print("inside")\n',
+ 'doc_path': '/reference/datamodel.html#special-method-names',
+ 'doc_url': 'https://docs.python.org/3.13/reference/datamodel.html#special-method-names',
+ 'expected_output': '2\n'
+ "['a', 'b']\n"
+ 'a, b\n'
+ "Bag(['a', 'b'])\n"
+ 'True\n'
+ 'True\n'
+ 'True\n'
+ 'True\n'
+ 'a\n'
+ "['a', 'z']\n"
+ 'False\n'
+ '15\n'
+ 'enter\n'
+ 'inside\n'
+ 'exit\n',
+ 'explanation': ['Special methods, often called dunder methods, connect user-defined classes to '
+ 'Python syntax and built-ins such as len(), iter(), and repr().',
+ 'Implementing these methods lets your objects participate in Python protocols '
+ 'rather than forcing callers to learn custom method names for common operations.',
+ 'Good special methods make objects feel boring in the best way: they work with '
+ 'the language features Python programmers already know.'],
+ 'min_python': None,
+ 'notes': ["Dunder methods are looked up by Python's data model protocols.",
+ '`__repr__` is the developer-facing form; `__str__` is the user-facing form. `print()` '
+ 'falls back to `__repr__` when `__str__` is missing.',
+ 'Defining `__eq__` removes the default `__hash__`; restore it when the type should be '
+ 'hashable.',
+ 'Container protocols (`__contains__`, `__getitem__`, `__setitem__`, `__bool__`) make '
+ 'instances behave like built-in containers.',
+ '`__call__` makes instances callable; `__enter__`/`__exit__` make them context '
+ 'managers.',
+ 'Implement the smallest protocol that makes your object feel native.'],
+ 'section': 'Data Model',
+ 'see_also': ['container-protocols',
+ 'operator-overloading',
+ 'callable-objects',
+ 'context-managers'],
+ 'slug': 'special-methods',
+ 'summary': 'Special methods connect your objects to Python syntax and built-ins.',
+ 'title': 'Special Methods',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'class Bag:\n'
+ ' def __init__(self, items):\n'
+ ' self.items = list(items)\n'
+ '\n'
+ 'bag = Bag(["a", "b"])\n'
+ 'print(bag.items)',
+ 'prose': 'Start with a normal class that stores its data. Special methods build '
+ 'on ordinary instance state.'},
+ {'code': 'class Bag:\n'
+ ' def __init__(self, items):\n'
+ ' self.items = list(items)\n'
+ '\n'
+ ' def __len__(self):\n'
+ ' return len(self.items)\n'
+ '\n'
+ 'bag = Bag(["a", "b"])\n'
+ 'print(len(bag))',
+ 'prose': 'Implement `__len__` to let `len()` ask the object for its size using '
+ "Python's standard protocol."},
+ {'code': 'class Bag:\n'
+ ' def __init__(self, items):\n'
+ ' self.items = list(items)\n'
+ '\n'
+ ' def __len__(self):\n'
+ ' return len(self.items)\n'
+ '\n'
+ ' def __iter__(self):\n'
+ ' return iter(self.items)\n'
+ '\n'
+ 'bag = Bag(["a", "b"])\n'
+ 'print(list(bag))',
+ 'prose': 'Implement `__iter__` to make the object iterable. Then tools such as '
+ '`list()` can consume it without a custom method name.'},
+ {'code': 'class Bag:\n'
+ ' def __init__(self, items):\n'
+ ' self.items = list(items)\n'
+ '\n'
+ ' def __len__(self):\n'
+ ' return len(self.items)\n'
+ '\n'
+ ' def __iter__(self):\n'
+ ' return iter(self.items)\n'
+ '\n'
+ ' def __repr__(self):\n'
+ ' return f"Bag({self.items!r})"\n'
+ '\n'
+ 'bag = Bag(["a", "b"])\n'
+ 'print(bag)',
+ 'prose': 'Implement `__repr__` to give the object a useful developer-facing '
+ 'representation when it is printed or inspected. With no `__str__` '
+ 'defined, `print()` falls back to `__repr__`.'},
+ {'code': 'class Bag:\n'
+ ' def __init__(self, items):\n'
+ ' self.items = list(items)\n'
+ '\n'
+ ' def __repr__(self):\n'
+ ' return f"Bag({self.items!r})"\n'
+ '\n'
+ ' def __str__(self):\n'
+ ' return ", ".join(self.items)\n'
+ '\n'
+ 'bag = Bag(["a", "b"])\n'
+ 'print(bag)\n'
+ 'print(repr(bag))',
+ 'prose': 'Add `__str__` for an end-user representation. `print()` and `str()` '
+ 'prefer `__str__`; `repr()` and the REPL still use `__repr__`. Keep '
+ '`__repr__` unambiguous for debugging and let `__str__` be the '
+ 'friendly form.'},
+ {'code': 'class Bag:\n'
+ ' def __init__(self, items):\n'
+ ' self.items = list(items)\n'
+ '\n'
+ ' def __eq__(self, other):\n'
+ ' return isinstance(other, Bag) and self.items == other.items\n'
+ '\n'
+ ' def __hash__(self):\n'
+ ' return hash(tuple(self.items))\n'
+ '\n'
+ ' def __lt__(self, other):\n'
+ ' return len(self.items) < len(other.items)\n'
+ '\n'
+ 'print(Bag(["a", "b"]) == Bag(["a", "b"]))\n'
+ 'print(Bag(["a"]) < Bag(["a", "b"]))\n'
+ 'print(hash(Bag(["a"])) == hash(Bag(["a"])))',
+ 'prose': '`__eq__` decides what equality means for the type. Defining `__eq__` '
+ 'removes the default `__hash__`, so add `__hash__` back when instances '
+ 'should work in sets or as dict keys. `__lt__` enables `<` and, with '
+ 'the rest of the order family, `sorted()`.'},
+ {'code': 'class Bag:\n'
+ ' def __init__(self, items):\n'
+ ' self.items = list(items)\n'
+ '\n'
+ ' def __contains__(self, item):\n'
+ ' return item in self.items\n'
+ '\n'
+ ' def __getitem__(self, index):\n'
+ ' return self.items[index]\n'
+ '\n'
+ ' def __setitem__(self, index, value):\n'
+ ' self.items[index] = value\n'
+ '\n'
+ ' def __bool__(self):\n'
+ ' return bool(self.items)\n'
+ '\n'
+ 'bag = Bag(["a", "b"])\n'
+ 'print("a" in bag)\n'
+ 'print(bag[0])\n'
+ 'bag[1] = "z"\n'
+ 'print(bag.items)\n'
+ 'print(bool(Bag([])))',
+ 'prose': 'The container protocols make instances behave like built-in '
+ 'containers. `__contains__` powers `in`, `__getitem__`/`__setitem__` '
+ 'power subscription, and `__bool__` decides truthiness for `if` and '
+ '`while`. See [container-protocols](/data-model/container-protocols) '
+ 'for the full surface.'},
+ {'code': 'class Multiplier:\n'
+ ' def __init__(self, factor):\n'
+ ' self.factor = factor\n'
+ '\n'
+ ' def __call__(self, value):\n'
+ ' return value * self.factor\n'
+ '\n'
+ 'triple = Multiplier(3)\n'
+ 'print(triple(5))\n'
+ '\n'
+ '\n'
+ 'class Trace:\n'
+ ' def __enter__(self):\n'
+ ' print("enter")\n'
+ ' return self\n'
+ '\n'
+ ' def __exit__(self, *exc):\n'
+ ' print("exit")\n'
+ ' return False\n'
+ '\n'
+ 'with Trace():\n'
+ ' print("inside")',
+ 'prose': '`__call__` makes an instance callable like a function — useful for '
+ 'stateful operations whose configuration deserves a name. `__enter__` '
+ 'and `__exit__` make a class a context manager so it can be used with '
+ '`with`. The focused [callable-objects](/data-model/callable-objects) '
+ 'and [context-managers](/data-model/context-managers) pages go '
+ 'deeper.'}]},
+ {'cells': [{'code': 'class Inbox:\n'
' def __init__(self, messages):\n'
' self.messages = list(messages)\n'
'\n'
@@ -2692,16 +5862,17 @@
' return len(self.messages)\n'
'\n'
'print(len(Inbox(["hi", "bye"])))',
- 'output': '2',
+ 'kind': 'cell',
'line': 22,
- 'kind': 'cell'},
- {'prose': ['If a class has `__len__` but no `__bool__`, Python uses zero length as false.'],
- 'code': 'print(bool(Inbox([])))',
- 'output': 'False',
+ 'output': '2',
+ 'prose': ['`__len__` lets `len()` ask an object for its size.']},
+ {'code': 'print(bool(Inbox([])))',
+ 'kind': 'cell',
'line': 41,
- 'kind': 'cell'},
- {'prose': ['`__bool__` expresses truth directly when the answer is not just container size.'],
- 'code': 'class Account:\n'
+ 'output': 'False',
+ 'prose': ['If a class has `__len__` but no `__bool__`, Python uses zero length as '
+ 'false.']},
+ {'code': 'class Account:\n'
' def __init__(self, active):\n'
' self.active = active\n'
'\n'
@@ -2709,37 +5880,75 @@
' return self.active\n'
'\n'
'print(bool(Account(False)))',
- 'output': 'False',
+ 'kind': 'cell',
'line': 53,
- 'kind': 'cell'}]},
- {'slug': 'container-protocols',
- 'title': 'Container Protocols',
- 'section': 'Data Model',
- 'summary': 'Container methods connect objects to indexing, membership, and item assignment.',
- 'doc_url': 'https://docs.python.org/3.13/reference/datamodel.html#emulating-container-types',
- 'code': 'class Scores:\n'
- ' def __init__(self):\n'
- ' self._scores = {}\n'
+ 'output': 'False',
+ 'prose': ['`__bool__` expresses truth directly when the answer is not just container '
+ 'size.']}],
+ 'code': 'class Inbox:\n'
+ ' def __init__(self, messages):\n'
+ ' self.messages = list(messages)\n'
'\n'
- ' def __contains__(self, name):\n'
- ' return name in self._scores\n'
+ ' def __len__(self):\n'
+ ' return len(self.messages)\n'
'\n'
- ' def __getitem__(self, name):\n'
- ' return self._scores[name]\n'
+ 'class Account:\n'
+ ' def __init__(self, active):\n'
+ ' self.active = active\n'
'\n'
- ' def __setitem__(self, name, score):\n'
- ' self._scores[name] = score\n'
+ ' def __bool__(self):\n'
+ ' return self.active\n'
'\n'
- 'scores = Scores()\n'
- 'scores["Ada"] = 98\n'
- 'print("Ada" in scores)\n'
- 'print(scores["Ada"])\n',
- 'expected_output': 'True\n98\n',
- 'notes': ['Implement the narrowest container protocol your object needs.',
- 'Use `KeyError` and `IndexError` consistently with built-in containers.',
- 'If a plain `dict` or `list` is enough, prefer it over a custom container.'],
- 'cells': [{'prose': ['`__setitem__` gives assignment syntax to a custom container.'],
- 'code': 'class Scores:\n'
+ 'print(len(Inbox(["hi", "bye"])))\n'
+ 'print(bool(Inbox([])))\n'
+ 'print(bool(Account(False)))\n',
+ 'doc_path': '/reference/datamodel.html#object.__bool__',
+ 'doc_url': 'https://docs.python.org/3.13/reference/datamodel.html#object.__bool__',
+ 'expected_output': '2\nFalse\nFalse\n',
+ 'explanation': ['Truth tests ask an object whether it should count as true. Containers usually '
+ 'answer through their size, while domain objects can answer with `__bool__` when '
+ 'emptiness is not the right idea.',
+ '`__len__` supports `len(obj)` and also provides a fallback truth value: length '
+ 'zero is false, non-zero length is true. `__bool__` is more direct and wins when '
+ 'both are present.',
+ 'Use these methods to match the meaning of your object. A queue can be false '
+ 'when it has no items; an account might be true only when it is active, '
+ 'regardless of its balance.'],
+ 'min_python': None,
+ 'notes': ['Prefer `__len__` for sized containers.',
+ 'Prefer `__bool__` when truth has domain meaning.',
+ 'Keep truth tests unsurprising; surprising falsy objects make conditionals harder to '
+ 'read.'],
+ 'section': 'Data Model',
+ 'see_also': ['truthiness', 'special-methods', 'container-protocols'],
+ 'slug': 'truth-and-size',
+ 'summary': '__bool__ and __len__ decide how objects behave in truth tests and len().',
+ 'title': 'Truth and Size',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'class Inbox:\n'
+ ' def __init__(self, messages):\n'
+ ' self.messages = list(messages)\n'
+ '\n'
+ ' def __len__(self):\n'
+ ' return len(self.messages)\n'
+ '\n'
+ 'print(len(Inbox(["hi", "bye"])))',
+ 'prose': '`__len__` lets `len()` ask an object for its size.'},
+ {'code': 'print(bool(Inbox([])))',
+ 'prose': 'If a class has `__len__` but no `__bool__`, Python uses zero length '
+ 'as false.'},
+ {'code': 'class Account:\n'
+ ' def __init__(self, active):\n'
+ ' self.active = active\n'
+ '\n'
+ ' def __bool__(self):\n'
+ ' return self.active\n'
+ '\n'
+ 'print(bool(Account(False)))',
+ 'prose': '`__bool__` expresses truth directly when the answer is not just '
+ 'container size.'}]},
+ {'cells': [{'code': 'class Scores:\n'
' def __init__(self):\n'
' self._scores = {}\n'
'\n'
@@ -2749,11 +5958,11 @@
'scores = Scores()\n'
'scores["Ada"] = 98\n'
'print(scores._scores)',
- 'output': "{'Ada': 98}",
+ 'kind': 'cell',
'line': 22,
- 'kind': 'cell'},
- {'prose': ['`__contains__` answers membership tests written with `in`.'],
- 'code': 'class Scores:\n'
+ 'output': "{'Ada': 98}",
+ 'prose': ['`__setitem__` gives assignment syntax to a custom container.']},
+ {'code': 'class Scores:\n'
' def __init__(self):\n'
' self._scores = {"Ada": 98}\n'
'\n'
@@ -2762,11 +5971,11 @@
'\n'
'scores = Scores()\n'
'print("Ada" in scores)',
- 'output': 'True',
+ 'kind': 'cell',
'line': 43,
- 'kind': 'cell'},
- {'prose': ['`__getitem__` connects bracket lookup to your internal storage.'],
- 'code': 'class Scores:\n'
+ 'output': 'True',
+ 'prose': ['`__contains__` answers membership tests written with `in`.']},
+ {'code': 'class Scores:\n'
' def __init__(self):\n'
' self._scores = {"Ada": 98}\n'
'\n'
@@ -2775,44 +5984,93 @@
'\n'
'scores = Scores()\n'
'print(scores["Ada"])',
- 'output': '98',
+ 'kind': 'cell',
'line': 63,
- 'kind': 'cell'}]},
- {'slug': 'callable-objects',
- 'title': 'Callable Objects',
- 'section': 'Data Model',
- 'summary': '__call__ lets an instance behave like a function while keeping state.',
- 'doc_url': 'https://docs.python.org/3.13/reference/datamodel.html#object.__call__',
- 'code': 'class Multiplier:\n'
- ' def __init__(self, factor):\n'
- ' self.factor = factor\n'
- ' self.calls = 0\n'
+ 'output': '98',
+ 'prose': ['`__getitem__` connects bracket lookup to your internal storage.']}],
+ 'code': 'class Scores:\n'
+ ' def __init__(self):\n'
+ ' self._scores = {}\n'
'\n'
- ' def __call__(self, value):\n'
- ' self.calls += 1\n'
- ' return value * self.factor\n'
+ ' def __contains__(self, name):\n'
+ ' return name in self._scores\n'
'\n'
- 'double = Multiplier(2)\n'
- 'print(double(5))\n'
- 'print(double(7))\n'
- 'print(double.calls)\n',
- 'expected_output': '10\n14\n2\n',
- 'notes': ['`callable(obj)` checks whether an object can be called.',
- 'Callable objects are good for named, stateful behavior.',
- 'Prefer plain functions when no instance state is needed.'],
- 'cells': [{'prose': ['A callable object starts as ordinary state stored on an instance.'],
- 'code': 'class Multiplier:\n'
+ ' def __getitem__(self, name):\n'
+ ' return self._scores[name]\n'
+ '\n'
+ ' def __setitem__(self, name, score):\n'
+ ' self._scores[name] = score\n'
+ '\n'
+ 'scores = Scores()\n'
+ 'scores["Ada"] = 98\n'
+ 'print("Ada" in scores)\n'
+ 'print(scores["Ada"])\n',
+ 'doc_path': '/reference/datamodel.html#emulating-container-types',
+ 'doc_url': 'https://docs.python.org/3.13/reference/datamodel.html#emulating-container-types',
+ 'expected_output': 'True\n98\n',
+ 'explanation': ['Container protocols let a class behave like the collection it represents. '
+ 'Instead of inventing method names such as `has()` or `lookup()`, the object can '
+ 'support `in`, indexing, and assignment.',
+ 'The key methods are small and familiar: `__contains__` powers `in`, '
+ '`__getitem__` powers `obj[key]`, and `__setitem__` powers `obj[key] = value`. '
+ 'Add only the operations the object can honestly support.',
+ "This keeps the public interface aligned with Python's built-in containers. "
+ 'Callers can use the same syntax for custom records, caches, tables, and '
+ 'sequence-like objects.'],
+ 'min_python': None,
+ 'notes': ['Implement the narrowest container protocol your object needs.',
+ 'Use `KeyError` and `IndexError` consistently with built-in containers.',
+ 'If a plain `dict` or `list` is enough, prefer it over a custom container.'],
+ 'section': 'Data Model',
+ 'see_also': ['lists', 'dicts', 'special-methods'],
+ 'slug': 'container-protocols',
+ 'summary': 'Container methods connect objects to indexing, membership, and item assignment.',
+ 'title': 'Container Protocols',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'class Scores:\n'
+ ' def __init__(self):\n'
+ ' self._scores = {}\n'
+ '\n'
+ ' def __setitem__(self, name, score):\n'
+ ' self._scores[name] = score\n'
+ '\n'
+ 'scores = Scores()\n'
+ 'scores["Ada"] = 98\n'
+ 'print(scores._scores)',
+ 'prose': '`__setitem__` gives assignment syntax to a custom container.'},
+ {'code': 'class Scores:\n'
+ ' def __init__(self):\n'
+ ' self._scores = {"Ada": 98}\n'
+ '\n'
+ ' def __contains__(self, name):\n'
+ ' return name in self._scores\n'
+ '\n'
+ 'scores = Scores()\n'
+ 'print("Ada" in scores)',
+ 'prose': '`__contains__` answers membership tests written with `in`.'},
+ {'code': 'class Scores:\n'
+ ' def __init__(self):\n'
+ ' self._scores = {"Ada": 98}\n'
+ '\n'
+ ' def __getitem__(self, name):\n'
+ ' return self._scores[name]\n'
+ '\n'
+ 'scores = Scores()\n'
+ 'print(scores["Ada"])',
+ 'prose': '`__getitem__` connects bracket lookup to your internal storage.'}]},
+ {'cells': [{'code': 'class Multiplier:\n'
' def __init__(self, factor):\n'
' self.factor = factor\n'
' self.calls = 0\n'
'\n'
'double = Multiplier(2)\n'
'print(double.factor)',
+ 'kind': 'cell',
+ 'line': 23,
'output': '2',
- 'line': 22,
- 'kind': 'cell'},
- {'prose': ['`__call__` makes the instance usable with function-call syntax.'],
- 'code': 'class Multiplier:\n'
+ 'prose': ['A callable object starts as ordinary state stored on an instance.']},
+ {'code': 'class Multiplier:\n'
' def __init__(self, factor):\n'
' self.factor = factor\n'
' self.calls = 0\n'
@@ -2824,41 +6082,76 @@
'double = Multiplier(2)\n'
'print(double(5))\n'
'print(double(7))',
+ 'kind': 'cell',
+ 'line': 41,
'output': '10\n14',
- 'line': 40,
- 'kind': 'cell'},
- {'prose': ['Because the callable is still an object, it can remember state across calls.'],
- 'code': 'print(double.calls)',
+ 'prose': ['`__call__` makes the instance usable with function-call syntax.']},
+ {'code': 'print(double.calls)',
+ 'kind': 'cell',
+ 'line': 65,
'output': '2',
- 'line': 64,
- 'kind': 'cell'}]},
- {'slug': 'operator-overloading',
- 'title': 'Operator Overloading',
- 'section': 'Data Model',
- 'summary': 'Operator methods let objects define arithmetic and comparison syntax.',
- 'doc_url': 'https://docs.python.org/3.13/reference/datamodel.html#emulating-numeric-types',
- 'code': 'class Vector:\n'
- ' def __init__(self, x, y):\n'
- ' self.x = x\n'
- ' self.y = y\n'
- '\n'
- ' def __add__(self, other):\n'
- ' return Vector(self.x + other.x, self.y + other.y)\n'
- '\n'
- ' def __eq__(self, other):\n'
- ' return (self.x, self.y) == (other.x, other.y)\n'
+ 'prose': ['Because the callable is still an object, it can remember state across '
+ 'calls.']}],
+ 'code': 'class Multiplier:\n'
+ ' def __init__(self, factor):\n'
+ ' self.factor = factor\n'
+ ' self.calls = 0\n'
'\n'
- ' def __repr__(self):\n'
- ' return f"Vector({self.x}, {self.y})"\n'
+ ' def __call__(self, value):\n'
+ ' self.calls += 1\n'
+ ' return value * self.factor\n'
'\n'
- 'print(Vector(2, 3) + Vector(4, 5))\n'
- 'print(Vector(1, 1) == Vector(1, 1))\n',
- 'expected_output': 'Vector(6, 8)\nTrue\n',
- 'notes': ['Overload operators only when the operation is unsurprising.',
- 'Return `NotImplemented` when an operand type is unsupported.',
- 'Implement equality deliberately when value comparison matters.'],
- 'cells': [{'prose': ['`__add__` defines how the `+` operator combines two objects.'],
- 'code': 'class Vector:\n'
+ 'double = Multiplier(2)\n'
+ 'print(double(5))\n'
+ 'print(double(7))\n'
+ 'print(double.calls)\n',
+ 'doc_path': '/reference/datamodel.html#object.__call__',
+ 'doc_url': 'https://docs.python.org/3.13/reference/datamodel.html#object.__call__',
+ 'expected_output': '10\n14\n2\n',
+ 'explanation': ['Functions are not the only callable objects in Python. Any instance can be '
+ 'called with parentheses when its class defines `__call__`.',
+ 'Callable objects are useful when behavior needs remembered configuration or '
+ 'evolving state. A closure can do this too; a class is often clearer when the '
+ 'state has multiple fields or needs named methods.',
+ 'The tradeoff is ceremony. Use a function for simple behavior, a closure for '
+ 'small captured state, and a callable object when naming the state improves the '
+ 'interface.'],
+ 'min_python': None,
+ 'notes': ['`callable(obj)` checks whether an object can be called.',
+ 'Callable objects are good for named, stateful behavior.',
+ 'Prefer plain functions when no instance state is needed.'],
+ 'section': 'Data Model',
+ 'see_also': ['functions', 'closures', 'callable-types', 'bound-and-unbound-methods'],
+ 'slug': 'callable-objects',
+ 'summary': '__call__ lets an instance behave like a function while keeping state.',
+ 'title': 'Callable Objects',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'class Multiplier:\n'
+ ' def __init__(self, factor):\n'
+ ' self.factor = factor\n'
+ ' self.calls = 0\n'
+ '\n'
+ 'double = Multiplier(2)\n'
+ 'print(double.factor)',
+ 'prose': 'A callable object starts as ordinary state stored on an instance.'},
+ {'code': 'class Multiplier:\n'
+ ' def __init__(self, factor):\n'
+ ' self.factor = factor\n'
+ ' self.calls = 0\n'
+ '\n'
+ ' def __call__(self, value):\n'
+ ' self.calls += 1\n'
+ ' return value * self.factor\n'
+ '\n'
+ 'double = Multiplier(2)\n'
+ 'print(double(5))\n'
+ 'print(double(7))',
+ 'prose': '`__call__` makes the instance usable with function-call syntax.'},
+ {'code': 'print(double.calls)',
+ 'prose': 'Because the callable is still an object, it can remember state across '
+ 'calls.'}]},
+ {'cells': [{'code': 'class Vector:\n'
' def __init__(self, x, y):\n'
' self.x = x\n'
' self.y = y\n'
@@ -2870,12 +6163,11 @@
' return f"Vector({self.x}, {self.y})"\n'
'\n'
'print(Vector(2, 3) + Vector(4, 5))',
- 'output': 'Vector(6, 8)',
+ 'kind': 'cell',
'line': 22,
- 'kind': 'cell'},
- {'prose': ['`__eq__` defines value equality for `==`. Without it, user-defined objects compare by '
- 'identity.'],
- 'code': 'class Vector:\n'
+ 'output': 'Vector(6, 8)',
+ 'prose': ['`__add__` defines how the `+` operator combines two objects.']},
+ {'code': 'class Vector:\n'
' def __init__(self, x, y):\n'
' self.x = x\n'
' self.y = y\n'
@@ -2884,11 +6176,12 @@
' return (self.x, self.y) == (other.x, other.y)\n'
'\n'
'print(Vector(1, 1) == Vector(1, 1))',
- 'output': 'True',
+ 'kind': 'cell',
'line': 45,
- 'kind': 'cell'},
- {'prose': ['A useful `__repr__` makes operator results inspectable while debugging.'],
- 'code': 'class Vector:\n'
+ 'output': 'True',
+ 'prose': ['`__eq__` defines value equality for `==`. Without it, user-defined objects '
+ 'compare by identity.']},
+ {'code': 'class Vector:\n'
' def __init__(self, x, y):\n'
' self.x = x\n'
' self.y = y\n'
@@ -2900,14 +6193,133 @@
' return f"Vector({self.x}, {self.y})"\n'
'\n'
'print(repr(Vector(2, 3) + Vector(4, 5)))',
- 'output': 'Vector(6, 8)',
+ 'kind': 'cell',
'line': 65,
- 'kind': 'cell'}]},
- {'slug': 'attribute-access',
- 'title': 'Attribute Access',
+ 'output': 'Vector(6, 8)',
+ 'prose': ['A useful `__repr__` makes operator results inspectable while debugging.']}],
+ 'code': 'class Vector:\n'
+ ' def __init__(self, x, y):\n'
+ ' self.x = x\n'
+ ' self.y = y\n'
+ '\n'
+ ' def __add__(self, other):\n'
+ ' return Vector(self.x + other.x, self.y + other.y)\n'
+ '\n'
+ ' def __eq__(self, other):\n'
+ ' return (self.x, self.y) == (other.x, other.y)\n'
+ '\n'
+ ' def __repr__(self):\n'
+ ' return f"Vector({self.x}, {self.y})"\n'
+ '\n'
+ 'print(Vector(2, 3) + Vector(4, 5))\n'
+ 'print(Vector(1, 1) == Vector(1, 1))\n',
+ 'doc_path': '/reference/datamodel.html#emulating-numeric-types',
+ 'doc_url': 'https://docs.python.org/3.13/reference/datamodel.html#emulating-numeric-types',
+ 'expected_output': 'Vector(6, 8)\nTrue\n',
+ 'explanation': ['Operator overloading lets a class define what expressions such as `a + b` mean '
+ 'for its objects. This is useful when the operation is part of the domain '
+ 'vocabulary.',
+ 'The method should preserve the meaning readers expect from the operator. '
+ 'Vectors can add component by component; money can add amounts in the same '
+ 'currency; surprising overloads make code harder to trust.',
+ 'Python also has reflected methods such as `__radd__` for cases where the left '
+ 'operand does not know how to handle the right operand. That keeps mixed '
+ 'operations possible without making every type know every other type.'],
+ 'min_python': None,
+ 'notes': ['Overload operators only when the operation is unsurprising.',
+ 'Return `NotImplemented` when an operand type is unsupported.',
+ 'Implement equality deliberately when value comparison matters.'],
'section': 'Data Model',
- 'summary': 'Attribute hooks customize lookup, missing attributes, and assignment.',
- 'doc_url': 'https://docs.python.org/3.13/reference/datamodel.html#customizing-attribute-access',
+ 'see_also': ['operators', 'special-methods', 'equality-and-identity'],
+ 'slug': 'operator-overloading',
+ 'summary': 'Operator methods let objects define arithmetic and comparison syntax.',
+ 'title': 'Operator Overloading',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'class Vector:\n'
+ ' def __init__(self, x, y):\n'
+ ' self.x = x\n'
+ ' self.y = y\n'
+ '\n'
+ ' def __add__(self, other):\n'
+ ' return Vector(self.x + other.x, self.y + other.y)\n'
+ '\n'
+ ' def __repr__(self):\n'
+ ' return f"Vector({self.x}, {self.y})"\n'
+ '\n'
+ 'print(Vector(2, 3) + Vector(4, 5))',
+ 'prose': '`__add__` defines how the `+` operator combines two objects.'},
+ {'code': 'class Vector:\n'
+ ' def __init__(self, x, y):\n'
+ ' self.x = x\n'
+ ' self.y = y\n'
+ '\n'
+ ' def __eq__(self, other):\n'
+ ' return (self.x, self.y) == (other.x, other.y)\n'
+ '\n'
+ 'print(Vector(1, 1) == Vector(1, 1))',
+ 'prose': '`__eq__` defines value equality for `==`. Without it, user-defined '
+ 'objects compare by identity.'},
+ {'code': 'class Vector:\n'
+ ' def __init__(self, x, y):\n'
+ ' self.x = x\n'
+ ' self.y = y\n'
+ '\n'
+ ' def __add__(self, other):\n'
+ ' return Vector(self.x + other.x, self.y + other.y)\n'
+ '\n'
+ ' def __repr__(self):\n'
+ ' return f"Vector({self.x}, {self.y})"\n'
+ '\n'
+ 'print(repr(Vector(2, 3) + Vector(4, 5)))',
+ 'prose': 'A useful `__repr__` makes operator results inspectable while '
+ 'debugging.'}]},
+ {'cells': [{'code': 'class Settings:\n'
+ ' def __init__(self, values):\n'
+ ' self._values = dict(values)\n'
+ '\n'
+ 'settings = Settings({"theme": "dark"})\n'
+ 'print(settings._values)',
+ 'kind': 'cell',
+ 'line': 23,
+ 'output': "{'theme': 'dark'}",
+ 'prose': ['Normal initialization still needs to set real attributes. Calling '
+ '`object.__setattr__` avoids recursing through your own hook.']},
+ {'code': 'class Settings:\n'
+ ' def __init__(self, values):\n'
+ ' self._values = dict(values)\n'
+ '\n'
+ ' def __getattr__(self, name):\n'
+ ' try:\n'
+ ' return self._values[name]\n'
+ ' except KeyError as error:\n'
+ ' raise AttributeError(name) from error\n'
+ '\n'
+ 'settings = Settings({"theme": "dark"})\n'
+ 'print(settings.theme)',
+ 'kind': 'cell',
+ 'line': 40,
+ 'output': 'dark',
+ 'prose': ['`__getattr__` runs only for missing attributes, so it can provide fallback '
+ 'lookup.']},
+ {'code': 'class Settings:\n'
+ ' def __init__(self, values):\n'
+ ' self._values = dict(values)\n'
+ '\n'
+ ' def __setattr__(self, name, value):\n'
+ ' if name.startswith("_"):\n'
+ ' object.__setattr__(self, name, value)\n'
+ ' else:\n'
+ ' self._values[name] = value\n'
+ '\n'
+ 'settings = Settings({"theme": "dark"})\n'
+ 'settings.volume = 7\n'
+ 'print(settings._values["volume"])',
+ 'kind': 'cell',
+ 'line': 63,
+ 'output': '7',
+ 'prose': ['`__setattr__` intercepts assignment. This example stores public names in '
+ 'the backing dictionary.']}],
'code': 'class Settings:\n'
' def __init__(self, values):\n'
' self._values = dict(values)\n'
@@ -2928,60 +6340,260 @@
'print(settings.theme)\n'
'settings.volume = 7\n'
'print(settings._values["volume"])\n',
+ 'doc_path': '/reference/datamodel.html#customizing-attribute-access',
+ 'doc_url': 'https://docs.python.org/3.13/reference/datamodel.html#customizing-attribute-access',
'expected_output': 'dark\n7\n',
- 'notes': ['`__getattr__` is narrower than `__getattribute__` because it handles only missing attributes.',
+ 'explanation': ['Attribute access is usually simple: `obj.name` looks up an attribute. Python '
+ 'exposes hooks for the uncommon cases where lookup or assignment needs to be '
+ 'customized.',
+ '`__getattr__` runs only when normal lookup fails, which makes it a safer hook '
+ 'for computed fallback attributes. `__setattr__` runs for every assignment, so '
+ 'it should be used sparingly and carefully.',
+ 'Prefer ordinary attributes and `@property` first. Reach for these hooks when an '
+ 'object is intentionally adapting another interface, validating all assignments, '
+ 'or exposing dynamic fields.'],
+ 'min_python': None,
+ 'notes': ['`__getattr__` is narrower than `__getattribute__` because it handles only missing '
+ 'attributes.',
'`__setattr__` affects every assignment on the instance.',
- 'Use `property` or descriptors when the behavior is attached to a known attribute name.'],
- 'cells': [{'prose': ['Normal initialization still needs to set real attributes. Calling `object.__setattr__` avoids '
- 'recursing through your own hook.'],
- 'code': 'class Settings:\n'
- ' def __init__(self, values):\n'
- ' self._values = dict(values)\n'
+ 'Use `property` or descriptors when the behavior is attached to a known attribute '
+ 'name.'],
+ 'section': 'Data Model',
+ 'see_also': ['properties', 'descriptors', 'special-methods', 'bound-and-unbound-methods'],
+ 'slug': 'attribute-access',
+ 'summary': 'Attribute hooks customize lookup, missing attributes, and assignment.',
+ 'title': 'Attribute Access',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'class Settings:\n'
+ ' def __init__(self, values):\n'
+ ' self._values = dict(values)\n'
+ '\n'
+ 'settings = Settings({"theme": "dark"})\n'
+ 'print(settings._values)',
+ 'prose': 'Normal initialization still needs to set real attributes. Calling '
+ '`object.__setattr__` avoids recursing through your own hook.'},
+ {'code': 'class Settings:\n'
+ ' def __init__(self, values):\n'
+ ' self._values = dict(values)\n'
+ '\n'
+ ' def __getattr__(self, name):\n'
+ ' try:\n'
+ ' return self._values[name]\n'
+ ' except KeyError as error:\n'
+ ' raise AttributeError(name) from error\n'
+ '\n'
+ 'settings = Settings({"theme": "dark"})\n'
+ 'print(settings.theme)',
+ 'prose': '`__getattr__` runs only for missing attributes, so it can provide '
+ 'fallback lookup.'},
+ {'code': 'class Settings:\n'
+ ' def __init__(self, values):\n'
+ ' self._values = dict(values)\n'
+ '\n'
+ ' def __setattr__(self, name, value):\n'
+ ' if name.startswith("_"):\n'
+ ' object.__setattr__(self, name, value)\n'
+ ' else:\n'
+ ' self._values[name] = value\n'
+ '\n'
+ 'settings = Settings({"theme": "dark"})\n'
+ 'settings.volume = 7\n'
+ 'print(settings._values["volume"])',
+ 'prose': '`__setattr__` intercepts assignment. This example stores public names '
+ 'in the backing dictionary.'}]},
+ {'cells': [{'code': 'class Counter:\n'
+ ' def __init__(self, start=0):\n'
+ ' self.value = start\n'
'\n'
- 'settings = Settings({"theme": "dark"})\n'
- 'print(settings._values)',
- 'output': "{'theme': 'dark'}",
- 'line': 22,
- 'kind': 'cell'},
- {'prose': ['`__getattr__` runs only for missing attributes, so it can provide fallback lookup.'],
- 'code': 'class Settings:\n'
- ' def __init__(self, values):\n'
- ' self._values = dict(values)\n'
+ ' def increment(self):\n'
+ ' self.value += 1\n'
+ ' return self.value\n'
'\n'
- ' def __getattr__(self, name):\n'
- ' try:\n'
- ' return self._values[name]\n'
- ' except KeyError as error:\n'
- ' raise AttributeError(name) from error\n'
+ 'bound_counter = Counter(10)\n'
+ 'm = bound_counter.increment\n'
+ 'print(m.__self__ is bound_counter)\n'
+ 'print(m())\n'
+ 'print(m())',
+ 'kind': 'cell',
+ 'line': 23,
+ 'output': 'True\n11\n12',
+ 'prose': ['`instance.method` returns a bound method. The method already remembers the '
+ 'instance through `__self__`, so calling it does not require passing `self` '
+ 'again.']},
+ {'code': 'unbound_counter = Counter(0)\n'
+ 'unbound = Counter.increment\n'
+ 'print(type(unbound).__name__)\n'
+ 'print(unbound(unbound_counter))\n'
+ 'print(unbound(unbound_counter))',
+ 'kind': 'cell',
+ 'line': 49,
+ 'output': 'function\n1\n2',
+ 'prose': ['`Class.method` returns the underlying function — there is no `self` '
+ 'attached. Calling it requires passing the instance as the first argument '
+ 'explicitly. Using a fresh counter here makes the output independent of the '
+ 'previous cell.']},
+ {'code': 'handlers = []\n'
+ 'for _ in range(2):\n'
+ ' handlers.append(Counter().increment)\n'
+ '\n'
+ 'print(handlers[0]())\n'
+ 'print(handlers[0]())\n'
+ 'print(handlers[1]())',
+ 'kind': 'cell',
+ 'line': 67,
+ 'output': '1\n2\n1',
+ 'prose': ['Bound methods are first-class values. They can be stored in lists, passed '
+ 'to other functions, and called later. Each bound method carries its own '
+ '`__self__`, so two methods produced from two different instances stay '
+ 'independent.']},
+ {'code': 'descriptor_counter = Counter(0)\n'
+ 'func = Counter.__dict__["increment"]\n'
+ 'print(type(func).__name__)\n'
+ 'rebound = func.__get__(descriptor_counter, Counter)\n'
+ 'print(type(rebound).__name__)\n'
+ 'print(rebound.__self__ is descriptor_counter)',
+ 'kind': 'cell',
+ 'line': 87,
+ 'output': 'function\nmethod\nTrue',
+ 'prose': ['The binding is the descriptor protocol at work. The function lives on the '
+ 'class as a plain function; instance attribute access invokes `__get__`, '
+ 'which returns a bound method that knows the instance.']}],
+ 'code': 'class Counter:\n'
+ ' def __init__(self, start=0):\n'
+ ' self.value = start\n'
+ '\n'
+ ' def increment(self):\n'
+ ' self.value += 1\n'
+ ' return self.value\n'
+ '\n'
+ 'bound_counter = Counter(10)\n'
+ 'm = bound_counter.increment\n'
+ 'print(m.__self__ is bound_counter)\n'
+ 'print(m())\n'
+ 'print(m())\n'
+ '\n'
+ 'unbound_counter = Counter(0)\n'
+ 'unbound = Counter.increment\n'
+ 'print(type(unbound).__name__)\n'
+ 'print(unbound(unbound_counter))\n'
+ 'print(unbound(unbound_counter))\n'
+ '\n'
+ 'handlers = []\n'
+ 'for _ in range(2):\n'
+ ' handlers.append(Counter().increment)\n'
+ '\n'
+ 'print(handlers[0]())\n'
+ 'print(handlers[0]())\n'
+ 'print(handlers[1]())\n'
+ '\n'
+ 'descriptor_counter = Counter(0)\n'
+ 'func = Counter.__dict__["increment"]\n'
+ 'print(type(func).__name__)\n'
+ 'rebound = func.__get__(descriptor_counter, Counter)\n'
+ 'print(type(rebound).__name__)\n'
+ 'print(rebound.__self__ is descriptor_counter)\n',
+ 'doc_path': '/reference/datamodel.html#instance-methods',
+ 'doc_url': 'https://docs.python.org/3.13/reference/datamodel.html#instance-methods',
+ 'expected_output': 'True\n11\n12\nfunction\n1\n2\n1\n2\n1\nfunction\nmethod\nTrue\n',
+ 'explanation': ['When you write `instance.method`, Python returns a bound method — a callable '
+ 'that already remembers which instance to pass as `self`. When you write '
+ '`Class.method`, you get the underlying function back, and calling it requires '
+ 'passing an instance yourself.',
+ 'That distinction is why methods can be stored in collections, passed as '
+ 'callbacks, and called later without losing track of the object they belong to. '
+ 'Each bound method carries its own `__self__`, so two callables produced from '
+ 'two different instances stay independent even when their underlying function is '
+ 'the same.',
+ 'The mechanism is the descriptor protocol: a function attached to a class '
+ 'implements `__get__`, and that hook turns attribute access on an instance into '
+ 'a bound method. The page does not need that detail to use methods, but it '
+ 'explains what is happening underneath.'],
+ 'min_python': None,
+ 'notes': ['`instance.method` produces a bound method whose `__self__` is the instance.',
+ '`Class.method` produces the plain function and requires you to pass the instance.',
+ 'Each bound method is its own object; storing one captures its instance.',
+ 'The binding is implemented by the descriptor protocol on the function object.'],
+ 'section': 'Data Model',
+ 'see_also': ['classes', 'attribute-access', 'descriptors', 'callable-objects'],
+ 'slug': 'bound-and-unbound-methods',
+ 'summary': 'instance.method binds self automatically; Class.method is a plain function.',
+ 'title': 'Bound and Unbound Methods',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'class Counter:\n'
+ ' def __init__(self, start=0):\n'
+ ' self.value = start\n'
+ '\n'
+ ' def increment(self):\n'
+ ' self.value += 1\n'
+ ' return self.value\n'
+ '\n'
+ 'bound_counter = Counter(10)\n'
+ 'm = bound_counter.increment\n'
+ 'print(m.__self__ is bound_counter)\n'
+ 'print(m())\n'
+ 'print(m())',
+ 'prose': '`instance.method` returns a bound method. The method already '
+ 'remembers the instance through `__self__`, so calling it does not '
+ 'require passing `self` again.'},
+ {'code': 'unbound_counter = Counter(0)\n'
+ 'unbound = Counter.increment\n'
+ 'print(type(unbound).__name__)\n'
+ 'print(unbound(unbound_counter))\n'
+ 'print(unbound(unbound_counter))',
+ 'prose': '`Class.method` returns the underlying function — there is no `self` '
+ 'attached. Calling it requires passing the instance as the first '
+ 'argument explicitly. Using a fresh counter here makes the output '
+ 'independent of the previous cell.'},
+ {'code': 'handlers = []\n'
+ 'for _ in range(2):\n'
+ ' handlers.append(Counter().increment)\n'
+ '\n'
+ 'print(handlers[0]())\n'
+ 'print(handlers[0]())\n'
+ 'print(handlers[1]())',
+ 'prose': 'Bound methods are first-class values. They can be stored in lists, '
+ 'passed to other functions, and called later. Each bound method '
+ 'carries its own `__self__`, so two methods produced from two '
+ 'different instances stay independent.'},
+ {'code': 'descriptor_counter = Counter(0)\n'
+ 'func = Counter.__dict__["increment"]\n'
+ 'print(type(func).__name__)\n'
+ 'rebound = func.__get__(descriptor_counter, Counter)\n'
+ 'print(type(rebound).__name__)\n'
+ 'print(rebound.__self__ is descriptor_counter)',
+ 'prose': 'The binding is the descriptor protocol at work. The function lives on '
+ 'the class as a plain function; instance attribute access invokes '
+ '`__get__`, which returns a bound method that knows the instance.'}]},
+ {'cells': [{'code': 'class Positive:\n'
+ ' def __set_name__(self, owner, name):\n'
+ ' self.private_name = "_" + name\n'
'\n'
- 'settings = Settings({"theme": "dark"})\n'
- 'print(settings.theme)',
- 'output': 'dark',
- 'line': 39,
- 'kind': 'cell'},
- {'prose': ['`__setattr__` intercepts assignment. This example stores public names in the backing '
- 'dictionary.'],
- 'code': 'class Settings:\n'
- ' def __init__(self, values):\n'
- ' self._values = dict(values)\n'
+ ' def __get__(self, obj, owner):\n'
+ ' return getattr(obj, self.private_name)\n'
'\n'
- ' def __setattr__(self, name, value):\n'
- ' if name.startswith("_"):\n'
- ' object.__setattr__(self, name, value)\n'
- ' else:\n'
- ' self._values[name] = value\n'
+ ' def __set__(self, obj, value):\n'
+ ' if value <= 0:\n'
+ ' raise ValueError("must be positive")\n'
+ ' setattr(obj, self.private_name, value)\n'
'\n'
- 'settings = Settings({"theme": "dark"})\n'
- 'settings.volume = 7\n'
- 'print(settings._values["volume"])',
- 'output': '7',
- 'line': 62,
- 'kind': 'cell'}]},
- {'slug': 'descriptors',
- 'title': 'Descriptors',
- 'section': 'Data Model',
- 'summary': 'Descriptors customize attribute access through __get__, __set__, or __delete__.',
- 'doc_url': 'https://docs.python.org/3.13/howto/descriptor.html',
+ 'class Product:\n'
+ ' price = Positive()\n'
+ '\n'
+ ' def __init__(self, price):\n'
+ ' self.price = price\n'
+ '\n'
+ 'item = Product(10)\n'
+ 'print(item.price)\n'
+ 'try:\n'
+ ' item.price = -1\n'
+ 'except ValueError as error:\n'
+ ' print(error)',
+ 'kind': 'cell',
+ 'line': 22,
+ 'output': '10\nmust be positive',
+ 'prose': ['A descriptor object lives on the class.']}],
'code': 'class Positive:\n'
' def __set_name__(self, owner, name):\n'
' self.private_name = "_" + name\n'
@@ -3006,43 +6618,75 @@
' item.price = -1\n'
'except ValueError as error:\n'
' print(error)\n',
+ 'doc_path': '/howto/descriptor.html',
+ 'doc_url': 'https://docs.python.org/3.13/howto/descriptor.html',
'expected_output': '10\nmust be positive\n',
+ 'explanation': ['Descriptors customize attribute access through __get__, __set__, or __delete__. '
+ 'It exists to make a common boundary explicit instead of leaving the behavior '
+ 'implicit in a larger program.',
+ 'Use it when the problem shape matches the example, and prefer simpler '
+ 'neighboring tools when the extra machinery would hide the intent. The notes '
+ 'call out the boundary so the feature stays practical rather than decorative.',
+ 'The example is small, deterministic, and focused on the semantic point. The '
+ 'complete source is editable below, while the walkthrough pairs the source with '
+ 'its output.'],
+ 'min_python': None,
'notes': ['A descriptor object lives on the class.',
'Attribute access on instances calls descriptor methods.',
'Properties, methods, and many ORMs build on the descriptor protocol.'],
- 'cells': [{'prose': ['A descriptor object lives on the class.'],
- 'code': 'class Positive:\n'
- ' def __set_name__(self, owner, name):\n'
- ' self.private_name = "_" + name\n'
- '\n'
- ' def __get__(self, obj, owner):\n'
- ' return getattr(obj, self.private_name)\n'
- '\n'
- ' def __set__(self, obj, value):\n'
- ' if value <= 0:\n'
- ' raise ValueError("must be positive")\n'
- ' setattr(obj, self.private_name, value)\n'
- '\n'
- 'class Product:\n'
- ' price = Positive()\n'
+ 'section': 'Data Model',
+ 'see_also': ['attribute-access', 'properties', 'bound-and-unbound-methods'],
+ 'slug': 'descriptors',
+ 'summary': 'Descriptors customize attribute access through __get__, __set__, or __delete__.',
+ 'title': 'Descriptors',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'class Positive:\n'
+ ' def __set_name__(self, owner, name):\n'
+ ' self.private_name = "_" + name\n'
+ '\n'
+ ' def __get__(self, obj, owner):\n'
+ ' return getattr(obj, self.private_name)\n'
+ '\n'
+ ' def __set__(self, obj, value):\n'
+ ' if value <= 0:\n'
+ ' raise ValueError("must be positive")\n'
+ ' setattr(obj, self.private_name, value)\n'
+ '\n'
+ 'class Product:\n'
+ ' price = Positive()\n'
+ '\n'
+ ' def __init__(self, price):\n'
+ ' self.price = price\n'
+ '\n'
+ 'item = Product(10)\n'
+ 'print(item.price)\n'
+ 'try:\n'
+ ' item.price = -1\n'
+ 'except ValueError as error:\n'
+ ' print(error)',
+ 'prose': 'A descriptor object lives on the class.'}]},
+ {'cells': [{'code': 'class Tagged(type):\n'
+ ' def __new__(mcls, name, bases, namespace):\n'
+ ' namespace["tag"] = name.lower()\n'
+ ' return super().__new__(mcls, name, bases, namespace)\n'
'\n'
- ' def __init__(self, price):\n'
- ' self.price = price\n'
+ 'print(Tagged.__name__)',
+ 'kind': 'cell',
+ 'line': 22,
+ 'output': 'Tagged',
+ 'prose': ['A metaclass customizes class creation. `__new__` receives the class name, '
+ 'bases, and namespace before the class object exists.']},
+ {'code': 'class Event(metaclass=Tagged):\n'
+ ' pass\n'
'\n'
- 'item = Product(10)\n'
- 'print(item.price)\n'
- 'try:\n'
- ' item.price = -1\n'
- 'except ValueError as error:\n'
- ' print(error)',
- 'output': '10\nmust be positive',
- 'line': 17,
- 'kind': 'cell'}]},
- {'slug': 'metaclasses',
- 'title': 'Metaclasses',
- 'section': 'Classes',
- 'summary': 'A metaclass customizes how classes themselves are created.',
- 'doc_url': 'https://docs.python.org/3.13/reference/datamodel.html#metaclasses',
+ 'print(Event.tag)\n'
+ 'print(type(Event).__name__)',
+ 'kind': 'cell',
+ 'line': 39,
+ 'output': 'event\nTagged',
+ 'prose': ['The `metaclass=` keyword applies that class-building rule. Here the '
+ 'metaclass adds a `tag` attribute to the new class.']}],
'code': 'class Tagged(type):\n'
' def __new__(mcls, name, bases, namespace):\n'
' namespace["tag"] = name.lower()\n'
@@ -3053,32 +6697,91 @@
'\n'
'print(Event.tag)\n'
'print(type(Event).__name__)\n',
+ 'doc_path': '/reference/datamodel.html#metaclasses',
+ 'doc_url': 'https://docs.python.org/3.13/reference/datamodel.html#metaclasses',
'expected_output': 'event\nTagged\n',
+ 'explanation': ['A metaclass is the class of a class. Most Python code never needs one, but the '
+ 'syntax appears in frameworks that register, validate, or modify classes as they '
+ 'are created.',
+ 'The `metaclass=` keyword in a class statement chooses the object that builds '
+ 'the class. This is advanced machinery; decorators and ordinary functions are '
+ 'usually simpler.',
+ 'Use metaclasses only when class creation itself is the problem being solved.'],
+ 'min_python': None,
'notes': ['Metaclasses customize class creation, not instance behavior directly.',
'Most code should prefer class decorators, functions, or ordinary inheritance.',
'You are most likely to meet metaclasses inside frameworks and ORMs.'],
- 'cells': [{'prose': ['A metaclass customizes class creation. `__new__` receives the class name, bases, and namespace '
- 'before the class object exists.'],
- 'code': 'class Tagged(type):\n'
- ' def __new__(mcls, name, bases, namespace):\n'
- ' namespace["tag"] = name.lower()\n'
- ' return super().__new__(mcls, name, bases, namespace)\n'
+ 'section': 'Classes',
+ 'see_also': ['classes', 'inheritance-and-super', 'special-methods'],
+ 'slug': 'metaclasses',
+ 'summary': 'A metaclass customizes how classes themselves are created.',
+ 'title': 'Metaclasses',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'class Tagged(type):\n'
+ ' def __new__(mcls, name, bases, namespace):\n'
+ ' namespace["tag"] = name.lower()\n'
+ ' return super().__new__(mcls, name, bases, namespace)\n'
+ '\n'
+ 'print(Tagged.__name__)',
+ 'prose': 'A metaclass customizes class creation. `__new__` receives the class '
+ 'name, bases, and namespace before the class object exists.'},
+ {'code': 'class Event(metaclass=Tagged):\n'
+ ' pass\n'
+ '\n'
+ 'print(Event.tag)\n'
+ 'print(type(Event).__name__)',
+ 'prose': 'The `metaclass=` keyword applies that class-building rule. Here the '
+ 'metaclass adds a `tag` attribute to the new class.'}]},
+ {'cells': [{'code': 'class Tag:\n'
+ ' def __init__(self, name):\n'
+ ' self.name = name\n'
'\n'
- 'print(Tagged.__name__)',
- 'output': 'Tagged',
+ ' def __enter__(self):\n'
+ ' print(f"<{self.name}>")\n'
+ ' return self\n'
+ '\n'
+ ' def __exit__(self, exc_type, exc, tb):\n'
+ ' print(f"{self.name}>")\n'
+ ' return False\n'
+ '\n'
+ 'with Tag("section"):\n'
+ ' print("content")',
+ 'kind': 'cell',
'line': 22,
- 'kind': 'cell'},
- {'prose': ['The `metaclass=` keyword applies that class-building rule. Here the metaclass adds a `tag` '
- 'attribute to the new class.'],
- 'code': 'class Event(metaclass=Tagged):\n pass\n\nprint(Event.tag)\nprint(type(Event).__name__)',
- 'output': 'event\nTagged',
- 'line': 39,
- 'kind': 'cell'}]},
- {'slug': 'context-managers',
- 'title': 'Context Managers',
- 'section': 'Data Model',
- 'summary': 'with ensures setup and cleanup happen together.',
- 'doc_url': 'https://docs.python.org/3.13/reference/datamodel.html#context-managers',
+ 'output': '',
+ 'prose': ['A class-based context manager implements `__enter__` and `__exit__`. The '
+ 'value returned by `__enter__` is bound by `as` when the `with` statement '
+ 'uses it.']},
+ {'code': 'from contextlib import contextmanager\n'
+ '\n'
+ '@contextmanager\n'
+ 'def tag(name):\n'
+ ' print(f"<{name}>")\n'
+ ' try:\n'
+ ' yield\n'
+ ' finally:\n'
+ ' print(f"{name}>")\n'
+ '\n'
+ 'with tag("note"):\n'
+ ' print("body")',
+ 'kind': 'cell',
+ 'line': 49,
+ 'output': '\nbody\n',
+ 'prose': ['`contextlib.contextmanager` writes the same setup/cleanup shape as a '
+ 'generator. Code before `yield` is setup, and code after `yield` is '
+ 'cleanup.']},
+ {'code': 'try:\n'
+ ' with tag("error"):\n'
+ ' raise ValueError("boom")\n'
+ 'except ValueError:\n'
+ ' print("handled")',
+ 'kind': 'cell',
+ 'line': 74,
+ 'output': '\n\nhandled',
+ 'prose': ['Cleanup still runs when the block raises. Returning `False` from '
+ '`__exit__`, or letting a generator context manager re-raise, allows the '
+ 'exception to keep propagating.']}],
'code': 'from contextlib import contextmanager\n'
'\n'
'class Tag:\n'
@@ -3109,62 +6812,88 @@
' raise ValueError("boom")\n'
'except ValueError:\n'
' print("handled")\n',
+ 'doc_path': '/reference/datamodel.html#context-managers',
+ 'doc_url': 'https://docs.python.org/3.13/reference/datamodel.html#context-managers',
'expected_output': '\n\n\nhandled\n',
+ 'explanation': ['Context managers define setup and cleanup around a block of code. The `with` '
+ 'statement guarantees that cleanup runs when the block exits, even when an '
+ 'exception is raised.',
+ 'The protocol is powered by `__enter__` and `__exit__`. The '
+ '`contextlib.contextmanager` decorator is a concise way to write the same idea '
+ 'as a generator when a full class would be noisy.',
+ 'Production code often uses `with` for files, locks, transactions, temporary '
+ 'state, and resources that need reliable release.'],
+ 'min_python': None,
'notes': ['Files, locks, and temporary state commonly use context managers.',
'`__enter__` and `__exit__` power the protocol.',
'Use `finally` when cleanup must happen after errors too.',
'Returning true from `__exit__` suppresses an exception; do that only intentionally.'],
- 'cells': [{'prose': ['A class-based context manager implements `__enter__` and `__exit__`. The value returned by '
- '`__enter__` is bound by `as` when the `with` statement uses it.'],
- 'code': 'class Tag:\n'
- ' def __init__(self, name):\n'
- ' self.name = name\n'
- '\n'
- ' def __enter__(self):\n'
- ' print(f"<{self.name}>")\n'
- ' return self\n'
- '\n'
- ' def __exit__(self, exc_type, exc, tb):\n'
- ' print(f"{self.name}>")\n'
- ' return False\n'
- '\n'
- 'with Tag("section"):\n'
- ' print("content")',
- 'output': '',
- 'line': 22,
- 'kind': 'cell'},
- {'prose': ['`contextlib.contextmanager` writes the same setup/cleanup shape as a generator. Code before '
- '`yield` is setup, and code after `yield` is cleanup.'],
- 'code': 'from contextlib import contextmanager\n'
- '\n'
- '@contextmanager\n'
- 'def tag(name):\n'
- ' print(f"<{name}>")\n'
- ' try:\n'
- ' yield\n'
- ' finally:\n'
- ' print(f"{name}>")\n'
- '\n'
- 'with tag("note"):\n'
- ' print("body")',
- 'output': '\nbody\n',
- 'line': 49,
- 'kind': 'cell'},
- {'prose': ['Cleanup still runs when the block raises. Returning `False` from `__exit__`, or letting a '
- 'generator context manager re-raise, allows the exception to keep propagating.'],
- 'code': 'try:\n'
- ' with tag("error"):\n'
- ' raise ValueError("boom")\n'
- 'except ValueError:\n'
- ' print("handled")',
- 'output': '\n\nhandled',
- 'line': 74,
- 'kind': 'cell'}]},
- {'slug': 'delete-statements',
- 'title': 'Delete Statements',
'section': 'Data Model',
- 'summary': 'del removes bindings, items, and attributes rather than producing a value.',
- 'doc_url': 'https://docs.python.org/3.13/reference/simple_stmts.html#the-del-statement',
+ 'see_also': ['exceptions', 'special-methods', 'descriptors'],
+ 'slug': 'context-managers',
+ 'summary': 'with ensures setup and cleanup happen together.',
+ 'title': 'Context Managers',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'class Tag:\n'
+ ' def __init__(self, name):\n'
+ ' self.name = name\n'
+ '\n'
+ ' def __enter__(self):\n'
+ ' print(f"<{self.name}>")\n'
+ ' return self\n'
+ '\n'
+ ' def __exit__(self, exc_type, exc, tb):\n'
+ ' print(f"{self.name}>")\n'
+ ' return False\n'
+ '\n'
+ 'with Tag("section"):\n'
+ ' print("content")',
+ 'prose': 'A class-based context manager implements `__enter__` and `__exit__`. '
+ 'The value returned by `__enter__` is bound by `as` when the `with` '
+ 'statement uses it.'},
+ {'code': 'from contextlib import contextmanager\n'
+ '\n'
+ '@contextmanager\n'
+ 'def tag(name):\n'
+ ' print(f"<{name}>")\n'
+ ' try:\n'
+ ' yield\n'
+ ' finally:\n'
+ ' print(f"{name}>")\n'
+ '\n'
+ 'with tag("note"):\n'
+ ' print("body")',
+ 'prose': '`contextlib.contextmanager` writes the same setup/cleanup shape as a '
+ 'generator. Code before `yield` is setup, and code after `yield` is '
+ 'cleanup.'},
+ {'code': 'try:\n'
+ ' with tag("error"):\n'
+ ' raise ValueError("boom")\n'
+ 'except ValueError:\n'
+ ' print("handled")',
+ 'prose': 'Cleanup still runs when the block raises. Returning `False` from '
+ '`__exit__`, or letting a generator context manager re-raise, allows '
+ 'the exception to keep propagating.'}]},
+ {'cells': [{'code': 'profile = {"name": "Ada", "temporary": True}\n'
+ 'del profile["temporary"]\n'
+ 'print(profile)',
+ 'kind': 'cell',
+ 'line': 22,
+ 'output': "{'name': 'Ada'}",
+ 'prose': ['Deleting a dictionary key mutates the dictionary. The key is gone; it has '
+ 'not been set to `None`.']},
+ {'code': 'items = ["a", "b", "c"]\ndel items[1]\nprint(items)',
+ 'kind': 'cell',
+ 'line': 36,
+ 'output': "['a', 'c']",
+ 'prose': ['Deleting a list item removes that position and shifts later items left.']},
+ {'code': 'value = "cached"\ndel value\nprint("value" in locals())',
+ 'kind': 'cell',
+ 'line': 50,
+ 'output': 'False',
+ 'prose': ['Deleting a name removes the binding from the current namespace. It is '
+ 'different from rebinding the name to `None`.']}],
'code': 'profile = {"name": "Ada", "temporary": True}\n'
'del profile["temporary"]\n'
'print(profile)\n'
@@ -3176,51 +6905,38 @@
'value = "cached"\n'
'del value\n'
'print("value" in locals())\n',
+ 'doc_path': '/reference/simple_stmts.html#the-del-statement',
+ 'doc_url': 'https://docs.python.org/3.13/reference/simple_stmts.html#the-del-statement',
'expected_output': "{'name': 'Ada'}\n['a', 'c']\nFalse\n",
+ 'explanation': ['`del` removes a binding or an item. It is a statement, not a function, and it '
+ 'does not return the removed value.',
+ 'Use `del name` when a name should no longer be bound. Use `del mapping[key]` or '
+ '`del sequence[index]` when mutating a container by removing one part.',
+ 'This is different from assigning `None`: `None` is still a value, while `del` '
+ 'removes the binding or slot.'],
+ 'min_python': None,
'notes': ['`del` removes bindings or container entries.',
'Assign `None` when absence should remain an explicit value.',
'Use container methods such as `pop()` when you need the removed value back.'],
- 'cells': [{'prose': ['Deleting a dictionary key mutates the dictionary. The key is gone; it has not been set to '
- '`None`.'],
- 'code': 'profile = {"name": "Ada", "temporary": True}\ndel profile["temporary"]\nprint(profile)',
- 'output': "{'name': 'Ada'}",
- 'line': 22,
- 'kind': 'cell'},
- {'prose': ['Deleting a list item removes that position and shifts later items left.'],
- 'code': 'items = ["a", "b", "c"]\ndel items[1]\nprint(items)',
- 'output': "['a', 'c']",
- 'line': 36,
- 'kind': 'cell'},
- {'prose': ['Deleting a name removes the binding from the current namespace. It is different from rebinding '
- 'the name to `None`.'],
- 'code': 'value = "cached"\ndel value\nprint("value" in locals())',
- 'output': 'False',
- 'line': 50,
- 'kind': 'cell'}]},
- {'slug': 'exceptions',
- 'title': 'Exceptions',
- 'section': 'Errors',
- 'summary': 'Use try, except, else, and finally to separate success, recovery, and cleanup.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/errors.html',
- 'code': 'def parse_int(text):\n'
- ' return int(text)\n'
- '\n'
- 'for text in ["42", "python"]:\n'
- ' try:\n'
- ' number = parse_int(text)\n'
- ' except ValueError:\n'
- ' print(f"{text}: invalid")\n'
- ' else:\n'
- ' print(f"{text}: {number}")\n'
- ' finally:\n'
- ' print(f"checked {text}")\n',
- 'expected_output': '42: 42\nchecked 42\npython: invalid\nchecked python\n',
- 'notes': ['Catch the most specific exception you can.',
- '`else` is for success code that should run only if the `try` block did not fail.',
- '`finally` runs whether the operation succeeded or failed.'],
- 'cells': [{'prose': ['When no exception is raised, the `else` block runs. Keeping success in `else` makes the `try` '
- 'block contain only the operation that might fail.'],
- 'code': 'def parse_int(text):\n'
+ 'section': 'Data Model',
+ 'see_also': ['variables', 'dicts', 'mutability'],
+ 'slug': 'delete-statements',
+ 'summary': 'del removes bindings, items, and attributes rather than producing a value.',
+ 'title': 'Delete Statements',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'profile = {"name": "Ada", "temporary": True}\n'
+ 'del profile["temporary"]\n'
+ 'print(profile)',
+ 'prose': 'Deleting a dictionary key mutates the dictionary. The key is gone; it '
+ 'has not been set to `None`.'},
+ {'code': 'items = ["a", "b", "c"]\ndel items[1]\nprint(items)',
+ 'prose': 'Deleting a list item removes that position and shifts later items '
+ 'left.'},
+ {'code': 'value = "cached"\ndel value\nprint("value" in locals())',
+ 'prose': 'Deleting a name removes the binding from the current namespace. It is '
+ 'different from rebinding the name to `None`.'}]},
+ {'cells': [{'code': 'def parse_int(text):\n'
' return int(text)\n'
'\n'
'text = "42"\n'
@@ -3232,12 +6948,12 @@
' print(f"{text}: {number}")\n'
'finally:\n'
' print(f"checked {text}")',
- 'output': '42: 42\nchecked 42',
+ 'kind': 'cell',
'line': 19,
- 'kind': 'cell'},
- {'prose': ['When parsing fails, `int()` raises `ValueError`. Catching that specific exception makes the '
- 'expected recovery path explicit.'],
- 'code': 'text = "python"\n'
+ 'output': '42: 42\nchecked 42',
+ 'prose': ['When no exception is raised, the `else` block runs. Keeping success in '
+ '`else` makes the `try` block contain only the operation that might fail.']},
+ {'code': 'text = "python"\n'
'try:\n'
' number = parse_int(text)\n'
'except ValueError:\n'
@@ -3246,14 +6962,147 @@
' print(f"{text}: {number}")\n'
'finally:\n'
' print(f"checked {text}")',
- 'output': 'python: invalid\nchecked python',
+ 'kind': 'cell',
'line': 43,
- 'kind': 'cell'}]},
- {'slug': 'assertions',
- 'title': 'Assertions',
+ 'output': 'python: invalid\nchecked python',
+ 'prose': ['When parsing fails, `int()` raises `ValueError`. Catching that specific '
+ 'exception makes the expected recovery path explicit.']},
+ {'code': 'def safe_parse_broken(text):\n'
+ ' try:\n'
+ ' return int(text)\n'
+ ' except Exception:\n'
+ ' return None\n'
+ '\n'
+ 'def safe_parse_fixed(text):\n'
+ ' try:\n'
+ ' return int(text)\n'
+ ' except ValueError:\n'
+ ' return None\n'
+ '\n'
+ 'print(safe_parse_broken("42"))\n'
+ 'print(safe_parse_fixed("42"))',
+ 'kind': 'cell',
+ 'line': 64,
+ 'output': '42\n42',
+ 'prose': ['Bare `except:` and broad `except Exception:` swallow far more than the '
+ 'failure you meant to handle, including `KeyboardInterrupt` (bare) and most '
+ 'programming bugs (broad). Catch the specific class — `ValueError` here — '
+ 'so unexpected failures still surface.']}],
+ 'code': 'def parse_int(text):\n'
+ ' return int(text)\n'
+ '\n'
+ 'for text in ["42", "python"]:\n'
+ ' try:\n'
+ ' number = parse_int(text)\n'
+ ' except ValueError:\n'
+ ' print(f"{text}: invalid")\n'
+ ' else:\n'
+ ' print(f"{text}: {number}")\n'
+ ' finally:\n'
+ ' print(f"checked {text}")\n'
+ '\n'
+ '\n'
+ 'def safe_parse_broken(text):\n'
+ ' try:\n'
+ ' return int(text)\n'
+ ' except Exception:\n'
+ ' return None\n'
+ '\n'
+ 'def safe_parse_fixed(text):\n'
+ ' try:\n'
+ ' return int(text)\n'
+ ' except ValueError:\n'
+ ' return None\n'
+ '\n'
+ 'print(safe_parse_broken("42"))\n'
+ 'print(safe_parse_fixed("42"))\n',
+ 'doc_path': '/tutorial/errors.html',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/errors.html',
+ 'expected_output': '42: 42\nchecked 42\npython: invalid\nchecked python\n42\n42\n',
+ 'explanation': ['Exceptions represent errors or unusual conditions that interrupt normal control '
+ 'flow. `try` marks the operation that may fail, and `except` handles a specific '
+ 'failure where recovery makes sense.',
+ 'Keep the successful path separate from the recovery path. `else` runs only when '
+ 'no exception was raised, while `finally` runs either way for cleanup or '
+ 'bookkeeping.',
+ 'Use exceptions when an operation cannot produce a valid result. Prefer ordinary '
+ 'conditionals for expected branches that are not errors.',
+ 'Catch specific exceptions whenever possible. A broad catch can hide programming '
+ 'mistakes, while a targeted `ValueError` handler documents exactly what failure '
+ 'is expected.'],
+ 'min_python': None,
+ 'notes': ['Catch the most specific exception you can.',
+ '`else` is for success code that should run only if the `try` block did not fail.',
+ '`finally` runs whether the operation succeeded or failed.',
+ 'Avoid bare `except:` and broad `except Exception:` — they hide bugs and absorb '
+ 'signals like `KeyboardInterrupt`.'],
'section': 'Errors',
- 'summary': 'assert documents internal assumptions and fails loudly when they are false.',
- 'doc_url': 'https://docs.python.org/3.13/reference/simple_stmts.html#the-assert-statement',
+ 'see_also': [],
+ 'slug': 'exceptions',
+ 'summary': 'Use try, except, else, and finally to separate success, recovery, and cleanup.',
+ 'title': 'Exceptions',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'def parse_int(text):\n'
+ ' return int(text)\n'
+ '\n'
+ 'text = "42"\n'
+ 'try:\n'
+ ' number = parse_int(text)\n'
+ 'except ValueError:\n'
+ ' print(f"{text}: invalid")\n'
+ 'else:\n'
+ ' print(f"{text}: {number}")\n'
+ 'finally:\n'
+ ' print(f"checked {text}")',
+ 'prose': 'When no exception is raised, the `else` block runs. Keeping success '
+ 'in `else` makes the `try` block contain only the operation that might '
+ 'fail.'},
+ {'code': 'text = "python"\n'
+ 'try:\n'
+ ' number = parse_int(text)\n'
+ 'except ValueError:\n'
+ ' print(f"{text}: invalid")\n'
+ 'else:\n'
+ ' print(f"{text}: {number}")\n'
+ 'finally:\n'
+ ' print(f"checked {text}")',
+ 'prose': 'When parsing fails, `int()` raises `ValueError`. Catching that '
+ 'specific exception makes the expected recovery path explicit.'},
+ {'code': 'def safe_parse_broken(text):\n'
+ ' try:\n'
+ ' return int(text)\n'
+ ' except Exception:\n'
+ ' return None\n'
+ '\n'
+ 'def safe_parse_fixed(text):\n'
+ ' try:\n'
+ ' return int(text)\n'
+ ' except ValueError:\n'
+ ' return None\n'
+ '\n'
+ 'print(safe_parse_broken("42"))\n'
+ 'print(safe_parse_fixed("42"))',
+ 'prose': 'Bare `except:` and broad `except Exception:` swallow far more than '
+ 'the failure you meant to handle, including `KeyboardInterrupt` (bare) '
+ 'and most programming bugs (broad). Catch the specific class — '
+ '`ValueError` here — so unexpected failures still surface.'}]},
+ {'cells': [{'code': 'def average(scores):\n'
+ ' assert scores, "scores must not be empty"\n'
+ ' return sum(scores) / len(scores)\n'
+ '\n'
+ 'print(average([8, 10]))',
+ 'kind': 'cell',
+ 'line': 22,
+ 'output': '9.0',
+ 'prose': ['When the assertion is true, execution continues normally. The assertion '
+ "documents the function's internal expectation."]},
+ {'code': 'try:\n average([])\nexcept AssertionError as error:\n print(error)',
+ 'kind': 'cell',
+ 'line': 38,
+ 'output': 'scores must not be empty',
+ 'prose': ['When the assertion is false, Python raises `AssertionError`. This signals '
+ 'a broken assumption, not a normal recovery path.']}],
'code': 'def average(scores):\n'
' assert scores, "scores must not be empty"\n'
' return sum(scores) / len(scores)\n'
@@ -3264,31 +7113,68 @@
' average([])\n'
'except AssertionError as error:\n'
' print(error)\n',
+ 'doc_path': '/reference/simple_stmts.html#the-assert-statement',
+ 'doc_url': 'https://docs.python.org/3.13/reference/simple_stmts.html#the-assert-statement',
'expected_output': '9.0\nscores must not be empty\n',
+ 'explanation': ['`assert` checks an internal assumption. If the condition is false, Python '
+ 'raises `AssertionError` with an optional message.',
+ 'Use assertions for programmer assumptions, not for validating user input or '
+ 'external data. Input validation should raise ordinary exceptions that '
+ 'production code expects to handle.',
+ 'Assertions make invariants executable while keeping the successful path '
+ 'compact.'],
+ 'min_python': None,
'notes': ['Use `assert` for internal invariants and debugging assumptions.',
- 'Use explicit exceptions for user input, files, network responses, and other expected failures.',
- 'Assertions can be disabled with Python optimization flags, so do not rely on them for security checks.'],
- 'cells': [{'prose': ['When the assertion is true, execution continues normally. The assertion documents the '
- "function's internal expectation."],
- 'code': 'def average(scores):\n'
- ' assert scores, "scores must not be empty"\n'
- ' return sum(scores) / len(scores)\n'
+ 'Use explicit exceptions for user input, files, network responses, and other expected '
+ 'failures.',
+ 'Assertions can be disabled with Python optimization flags, so do not rely on them for '
+ 'security checks.'],
+ 'section': 'Errors',
+ 'see_also': ['exceptions', 'custom-exceptions', 'type-hints'],
+ 'slug': 'assertions',
+ 'summary': 'assert documents internal assumptions and fails loudly when they are false.',
+ 'title': 'Assertions',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'def average(scores):\n'
+ ' assert scores, "scores must not be empty"\n'
+ ' return sum(scores) / len(scores)\n'
+ '\n'
+ 'print(average([8, 10]))',
+ 'prose': 'When the assertion is true, execution continues normally. The '
+ "assertion documents the function's internal expectation."},
+ {'code': 'try:\n'
+ ' average([])\n'
+ 'except AssertionError as error:\n'
+ ' print(error)',
+ 'prose': 'When the assertion is false, Python raises `AssertionError`. This '
+ 'signals a broken assumption, not a normal recovery path.'}]},
+ {'cells': [{'code': 'class ConfigError(Exception):\n'
+ ' pass\n'
'\n'
- 'print(average([8, 10]))',
- 'output': '9.0',
+ '\n'
+ 'def read_port(text):\n'
+ ' try:\n'
+ ' return int(text)\n'
+ ' except ValueError as error:\n'
+ ' raise ConfigError("port must be a number") from error\n'
+ '\n'
+ 'print(ConfigError.__name__)',
+ 'kind': 'cell',
'line': 22,
- 'kind': 'cell'},
- {'prose': ['When the assertion is false, Python raises `AssertionError`. This signals a broken assumption, '
- 'not a normal recovery path.'],
- 'code': 'try:\n average([])\nexcept AssertionError as error:\n print(error)',
- 'output': 'scores must not be empty',
- 'line': 38,
- 'kind': 'cell'}]},
- {'slug': 'exception-chaining',
- 'title': 'Exception Chaining',
- 'section': 'Errors',
- 'summary': 'raise from preserves the original cause when translating exceptions.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/errors.html#exception-chaining',
+ 'output': 'ConfigError',
+ 'prose': ['Catch the low-level exception where it happens, then raise a '
+ 'domain-specific exception from it.']},
+ {'code': 'try:\n'
+ ' read_port("abc")\n'
+ 'except ConfigError as error:\n'
+ ' print(error)\n'
+ ' print(type(error.__cause__).__name__)',
+ 'kind': 'cell',
+ 'line': 44,
+ 'output': 'port must be a number\nValueError',
+ 'prose': ['The caller handles the domain error. The original `ValueError` remains '
+ 'available as `__cause__`.']}],
'code': 'class ConfigError(Exception):\n'
' pass\n'
'\n'
@@ -3306,41 +7192,68 @@
'except ConfigError as error:\n'
' print(error)\n'
' print(type(error.__cause__).__name__)\n',
+ 'doc_path': '/tutorial/errors.html#exception-chaining',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/errors.html#exception-chaining',
'expected_output': 'ConfigError\nport must be a number\nValueError\n',
+ 'explanation': ['Exception chaining connects a higher-level error to the lower-level exception '
+ 'that caused it. The syntax is `raise NewError(...) from error`.',
+ 'Use chaining when translating implementation details into a domain-specific '
+ 'error while preserving the original cause for debugging.',
+ 'This is different from hiding the original exception. The caller can catch the '
+ 'domain error, and tooling can still inspect `__cause__`.'],
+ 'min_python': None,
'notes': ['Use `raise ... from error` when translating exceptions across a boundary.',
"The new exception's `__cause__` points to the original exception.",
'Chaining keeps user-facing errors clear without losing debugging context.'],
- 'cells': [{'prose': ['Catch the low-level exception where it happens, then raise a domain-specific exception from '
- 'it.'],
- 'code': 'class ConfigError(Exception):\n'
- ' pass\n'
- '\n'
- '\n'
- 'def read_port(text):\n'
- ' try:\n'
- ' return int(text)\n'
- ' except ValueError as error:\n'
- ' raise ConfigError("port must be a number") from error\n'
- '\n'
- 'print(ConfigError.__name__)',
- 'output': 'ConfigError',
- 'line': 22,
- 'kind': 'cell'},
- {'prose': ['The caller handles the domain error. The original `ValueError` remains available as '
- '`__cause__`.'],
- 'code': 'try:\n'
- ' read_port("abc")\n'
- 'except ConfigError as error:\n'
- ' print(error)\n'
- ' print(type(error.__cause__).__name__)',
- 'output': 'port must be a number\nValueError',
- 'line': 44,
- 'kind': 'cell'}]},
- {'slug': 'exception-groups',
- 'title': 'Exception Groups',
'section': 'Errors',
- 'summary': 'except* handles matching exceptions inside an ExceptionGroup.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/errors.html#raising-and-handling-multiple-unrelated-exceptions',
+ 'see_also': ['exceptions', 'custom-exceptions', 'assertions'],
+ 'slug': 'exception-chaining',
+ 'summary': 'raise from preserves the original cause when translating exceptions.',
+ 'title': 'Exception Chaining',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'class ConfigError(Exception):\n'
+ ' pass\n'
+ '\n'
+ '\n'
+ 'def read_port(text):\n'
+ ' try:\n'
+ ' return int(text)\n'
+ ' except ValueError as error:\n'
+ ' raise ConfigError("port must be a number") from error\n'
+ '\n'
+ 'print(ConfigError.__name__)',
+ 'prose': 'Catch the low-level exception where it happens, then raise a '
+ 'domain-specific exception from it.'},
+ {'code': 'try:\n'
+ ' read_port("abc")\n'
+ 'except ConfigError as error:\n'
+ ' print(error)\n'
+ ' print(type(error.__cause__).__name__)',
+ 'prose': 'The caller handles the domain error. The original `ValueError` '
+ 'remains available as `__cause__`.'}]},
+ {'cells': [{'code': 'errors = ExceptionGroup(\n'
+ ' "batch failed",\n'
+ ' [ValueError("bad port"), TypeError("bad mode")],\n'
+ ')\n'
+ 'print(len(errors.exceptions))',
+ 'kind': 'cell',
+ 'line': 22,
+ 'output': '2',
+ 'prose': ['An exception group bundles several exception objects. This is different '
+ 'from an ordinary exception because more than one failure is present.']},
+ {'code': 'try:\n'
+ ' raise errors\n'
+ 'except* ValueError as group:\n'
+ ' print(type(group).__name__)\n'
+ ' print(group.exceptions[0])\n'
+ 'except* TypeError as group:\n'
+ ' print(group.exceptions[0])',
+ 'kind': 'cell',
+ 'line': 38,
+ 'output': 'ExceptionGroup\nbad port\nbad mode',
+ 'prose': ['`except*` handles matching members of the group. The `ValueError` handler '
+ 'sees the value error, and the `TypeError` handler sees the type error.']}],
'code': 'errors = ExceptionGroup(\n'
' "batch failed",\n'
' [ValueError("bad port"), TypeError("bad mode")],\n'
@@ -3354,37 +7267,63 @@
' print(group.exceptions[0])\n'
'except* TypeError as group:\n'
' print(group.exceptions[0])\n',
+ 'doc_path': '/tutorial/errors.html#raising-and-handling-multiple-unrelated-exceptions',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/errors.html#raising-and-handling-multiple-unrelated-exceptions',
'expected_output': '2\nExceptionGroup\nbad port\nbad mode\n',
+ 'explanation': ['`ExceptionGroup` represents several unrelated exceptions raised together. '
+ '`except*` exists for code that may receive multiple failures at once, '
+ 'especially concurrent work.',
+ 'Use ordinary `except` for one exception. Use `except*` only when the value '
+ 'being handled is an exception group and each matching subgroup needs its own '
+ 'handling.',
+ 'Each `except*` clause receives a smaller exception group containing the '
+ 'matching exceptions.'],
+ 'min_python': None,
'notes': ['`except*` is for `ExceptionGroup`, not ordinary single exceptions.',
'Each `except*` clause handles matching members of the group.',
'Exception groups often appear around concurrent work.'],
- 'cells': [{'prose': ['An exception group bundles several exception objects. This is different from an ordinary '
- 'exception because more than one failure is present.'],
- 'code': 'errors = ExceptionGroup(\n'
- ' "batch failed",\n'
- ' [ValueError("bad port"), TypeError("bad mode")],\n'
- ')\n'
- 'print(len(errors.exceptions))',
- 'output': '2',
- 'line': 22,
- 'kind': 'cell'},
- {'prose': ['`except*` handles matching members of the group. The `ValueError` handler sees the value '
- 'error, and the `TypeError` handler sees the type error.'],
- 'code': 'try:\n'
- ' raise errors\n'
- 'except* ValueError as group:\n'
- ' print(type(group).__name__)\n'
- ' print(group.exceptions[0])\n'
- 'except* TypeError as group:\n'
- ' print(group.exceptions[0])',
- 'output': 'ExceptionGroup\nbad port\nbad mode',
- 'line': 38,
- 'kind': 'cell'}]},
- {'slug': 'warnings',
- 'title': 'Warnings',
'section': 'Errors',
- 'summary': 'warnings report soft problems without immediately stopping the program.',
- 'doc_url': 'https://docs.python.org/3.13/library/warnings.html',
+ 'see_also': ['exceptions', 'exception-chaining', 'async-await'],
+ 'slug': 'exception-groups',
+ 'summary': 'except* handles matching exceptions inside an ExceptionGroup.',
+ 'title': 'Exception Groups',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'errors = ExceptionGroup(\n'
+ ' "batch failed",\n'
+ ' [ValueError("bad port"), TypeError("bad mode")],\n'
+ ')\n'
+ 'print(len(errors.exceptions))',
+ 'prose': 'An exception group bundles several exception objects. This is '
+ 'different from an ordinary exception because more than one failure is '
+ 'present.'},
+ {'code': 'try:\n'
+ ' raise errors\n'
+ 'except* ValueError as group:\n'
+ ' print(type(group).__name__)\n'
+ ' print(group.exceptions[0])\n'
+ 'except* TypeError as group:\n'
+ ' print(group.exceptions[0])',
+ 'prose': '`except*` handles matching members of the group. The `ValueError` '
+ 'handler sees the value error, and the `TypeError` handler sees the '
+ 'type error.'}]},
+ {'cells': [{'code': 'import warnings\n'
+ '\n'
+ '\n'
+ 'def old_name():\n'
+ ' warnings.warn("old_name is deprecated", DeprecationWarning, '
+ 'stacklevel=2)\n'
+ ' return "result"\n'
+ '\n'
+ 'warnings.simplefilter("always", DeprecationWarning)\n'
+ 'with warnings.catch_warnings(record=True) as caught:\n'
+ ' print(old_name())\n'
+ ' print(caught[0].category.__name__)\n'
+ ' print(str(caught[0].message))',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': 'result\nDeprecationWarning\nold_name is deprecated',
+ 'prose': ['Warnings are useful for deprecations and soft failures.']}],
'code': 'import warnings\n'
'\n'
'\n'
@@ -3397,31 +7336,72 @@
' print(old_name())\n'
' print(caught[0].category.__name__)\n'
' print(str(caught[0].message))\n',
+ 'doc_path': '/library/warnings.html',
+ 'doc_url': 'https://docs.python.org/3.13/library/warnings.html',
'expected_output': 'result\nDeprecationWarning\nold_name is deprecated\n',
+ 'explanation': ['warnings report soft problems without immediately stopping the program. It '
+ 'exists to make a common boundary explicit instead of leaving the behavior '
+ 'implicit in a larger program.',
+ 'Use it when the problem shape matches the example, and prefer simpler '
+ 'neighboring tools when the extra machinery would hide the intent. The notes '
+ 'call out the boundary so the feature stays practical rather than decorative.',
+ 'The example is small, deterministic, and focused on the semantic point. The '
+ 'complete source is editable below, while the walkthrough pairs the source with '
+ 'its output.'],
+ 'min_python': None,
'notes': ['Warnings are useful for deprecations and soft failures.',
'Filters decide whether warnings are ignored, shown, or turned into errors.',
'Tests often capture warnings to assert migration behavior.'],
- 'cells': [{'prose': ['Warnings are useful for deprecations and soft failures.'],
- 'code': 'import warnings\n'
- '\n'
- '\n'
- 'def old_name():\n'
- ' warnings.warn("old_name is deprecated", DeprecationWarning, stacklevel=2)\n'
- ' return "result"\n'
- '\n'
- 'warnings.simplefilter("always", DeprecationWarning)\n'
- 'with warnings.catch_warnings(record=True) as caught:\n'
- ' print(old_name())\n'
- ' print(caught[0].category.__name__)\n'
- ' print(str(caught[0].message))',
- 'output': 'result\nDeprecationWarning\nold_name is deprecated',
- 'line': 17,
- 'kind': 'cell'}]},
- {'slug': 'modules',
- 'title': 'Modules',
- 'section': 'Modules',
- 'summary': 'Modules organize code into namespaces and expose reusable definitions.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/modules.html',
+ 'section': 'Errors',
+ 'see_also': [],
+ 'slug': 'warnings',
+ 'summary': 'warnings report soft problems without immediately stopping the program.',
+ 'title': 'Warnings',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'import warnings\n'
+ '\n'
+ '\n'
+ 'def old_name():\n'
+ ' warnings.warn("old_name is deprecated", DeprecationWarning, '
+ 'stacklevel=2)\n'
+ ' return "result"\n'
+ '\n'
+ 'warnings.simplefilter("always", DeprecationWarning)\n'
+ 'with warnings.catch_warnings(record=True) as caught:\n'
+ ' print(old_name())\n'
+ ' print(caught[0].category.__name__)\n'
+ ' print(str(caught[0].message))',
+ 'prose': 'Warnings are useful for deprecations and soft failures.'}]},
+ {'cells': [{'code': 'import math\n'
+ '\n'
+ 'radius = 3\n'
+ 'area = math.pi * radius ** 2\n'
+ 'print(round(area, 2))',
+ 'kind': 'cell',
+ 'line': 21,
+ 'output': '28.27',
+ 'prose': ['Importing a module gives access to its namespace. The `math.` prefix makes '
+ 'it clear where `pi` came from.']},
+ {'code': 'from statistics import mean\n\nscores = [8, 10, 9]\nprint(mean(scores))',
+ 'kind': 'cell',
+ 'line': 37,
+ 'output': '9',
+ 'prose': ['A focused `from ... import ...` brings one definition into the current '
+ 'namespace. This keeps a common operation concise without importing every '
+ 'name.']},
+ {'code': 'print(math.__name__)',
+ 'kind': 'cell',
+ 'line': 52,
+ 'output': 'math',
+ 'prose': ['Modules are objects too. Their attributes include metadata such as '
+ "`__name__`, which records the module's import name."]},
+ {'code': 'import sys\nprint("math" in sys.modules)',
+ 'kind': 'cell',
+ 'line': 64,
+ 'output': 'True',
+ 'prose': ['Imported modules are cached in `sys.modules`. Later imports reuse the '
+ 'module object instead of executing the file again.']}],
'code': 'import math\n'
'import sys\n'
'from statistics import mean\n'
@@ -3435,40 +7415,69 @@
'\n'
'print(math.__name__)\n'
'print("math" in sys.modules)\n',
+ 'doc_path': '/tutorial/modules.html',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/modules.html',
'expected_output': '28.27\n9\nmath\nTrue\n',
+ 'explanation': ['Modules organize Python code into files and namespaces. `import` executes a '
+ "module once, stores it in Python's import cache, and gives your program access "
+ 'to its definitions.',
+ 'This page focuses on import forms and module namespaces. Package layout, '
+ 'aliases, and dynamic imports have their own neighboring examples.',
+ 'Use module namespaces such as `math.sqrt` when the source of a name should stay '
+ 'visible. Use focused imports such as `from statistics import mean` when the '
+ 'imported name is clear at the call site.'],
+ 'min_python': None,
'notes': ['Prefer plain `import module` when the namespace improves readability.',
'Use focused imports for a small number of clear names.',
'Place imports near the top of the file.',
'Imports execute module top-level code once, then reuse the cached module object.'],
- 'cells': [{'prose': ['Importing a module gives access to its namespace. The `math.` prefix makes it clear where `pi` '
- 'came from.'],
- 'code': 'import math\n\nradius = 3\narea = math.pi * radius ** 2\nprint(round(area, 2))',
- 'output': '28.27',
- 'line': 21,
- 'kind': 'cell'},
- {'prose': ['A focused `from ... import ...` brings one definition into the current namespace. This keeps a '
- 'common operation concise without importing every name.'],
- 'code': 'from statistics import mean\n\nscores = [8, 10, 9]\nprint(mean(scores))',
- 'output': '9',
- 'line': 37,
- 'kind': 'cell'},
- {'prose': ['Modules are objects too. Their attributes include metadata such as `__name__`, which records '
- "the module's import name."],
- 'code': 'print(math.__name__)',
- 'output': 'math',
- 'line': 52,
- 'kind': 'cell'},
- {'prose': ['Imported modules are cached in `sys.modules`. Later imports reuse the module object instead of '
- 'executing the file again.'],
- 'code': 'import sys\nprint("math" in sys.modules)',
- 'output': 'True',
- 'line': 64,
- 'kind': 'cell'}]},
- {'slug': 'import-aliases',
- 'title': 'Import Aliases',
'section': 'Modules',
- 'summary': 'as gives imported modules or names a local alias.',
- 'doc_url': 'https://docs.python.org/3.13/reference/simple_stmts.html#the-import-statement',
+ 'see_also': ['import-aliases', 'packages'],
+ 'slug': 'modules',
+ 'summary': 'Modules organize code into namespaces and expose reusable definitions.',
+ 'title': 'Modules',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'import math\n'
+ '\n'
+ 'radius = 3\n'
+ 'area = math.pi * radius ** 2\n'
+ 'print(round(area, 2))',
+ 'prose': 'Importing a module gives access to its namespace. The `math.` prefix '
+ 'makes it clear where `pi` came from.'},
+ {'code': 'from statistics import mean\n'
+ '\n'
+ 'scores = [8, 10, 9]\n'
+ 'print(mean(scores))',
+ 'prose': 'A focused `from ... import ...` brings one definition into the '
+ 'current namespace. This keeps a common operation concise without '
+ 'importing every name.'},
+ {'code': 'print(math.__name__)',
+ 'prose': 'Modules are objects too. Their attributes include metadata such as '
+ "`__name__`, which records the module's import name."},
+ {'code': 'import sys\nprint("math" in sys.modules)',
+ 'prose': 'Imported modules are cached in `sys.modules`. Later imports reuse the '
+ 'module object instead of executing the file again.'}]},
+ {'cells': [{'code': 'import statistics as stats\n'
+ '\n'
+ 'scores = [8, 10, 9]\n'
+ 'print(stats.mean(scores))\n'
+ 'print(stats.__name__)',
+ 'kind': 'cell',
+ 'line': 21,
+ 'output': '9\nstatistics',
+ 'prose': ['A module alias keeps the namespace but changes the local name. Here '
+ '`stats` is shorter, but readers can still see that `mean` belongs to the '
+ 'statistics module.']},
+ {'code': 'from math import sqrt as square_root\n'
+ '\n'
+ 'print(square_root(81))\n'
+ 'print(square_root.__name__)',
+ 'kind': 'cell',
+ 'line': 38,
+ 'output': '9.0\nsqrt',
+ 'prose': ['A name imported with `from` can also be aliased. Use this when the local '
+ 'name explains the role better than the original name.']}],
'code': 'import statistics as stats\n'
'from math import sqrt as square_root\n'
'\n'
@@ -3478,32 +7487,94 @@
'\n'
'print(square_root(81))\n'
'print(square_root.__name__)\n',
+ 'doc_path': '/reference/simple_stmts.html#the-import-statement',
+ 'doc_url': 'https://docs.python.org/3.13/reference/simple_stmts.html#the-import-statement',
'expected_output': '9\nstatistics\n9.0\nsqrt\n',
+ 'explanation': ['`as` gives an imported module or imported name a local alias. Use it when a '
+ 'conventional short name improves readability or when two imports would '
+ 'otherwise collide.',
+ 'The alternative is a plain import, which is usually better when the module name '
+ 'is already clear. Avoid aliases that make readers guess where a name came from.',
+ 'Avoid star imports in examples and production modules because they hide '
+ 'dependencies and blur the boundary between modules.'],
+ 'min_python': None,
'notes': ['`import module as alias` keeps module-style access under a shorter or clearer name.',
'`from module import name as alias` imports one name under a local alias.',
'Prefer plain imports unless an alias improves clarity or follows a strong convention.',
'Avoid `from module import *` because it makes dependencies harder to see.'],
- 'cells': [{'prose': ['A module alias keeps the namespace but changes the local name. Here `stats` is shorter, but '
- 'readers can still see that `mean` belongs to the statistics module.'],
- 'code': 'import statistics as stats\n'
- '\n'
- 'scores = [8, 10, 9]\n'
- 'print(stats.mean(scores))\n'
- 'print(stats.__name__)',
- 'output': '9\nstatistics',
- 'line': 21,
- 'kind': 'cell'},
- {'prose': ['A name imported with `from` can also be aliased. Use this when the local name explains the '
- 'role better than the original name.'],
- 'code': 'from math import sqrt as square_root\n\nprint(square_root(81))\nprint(square_root.__name__)',
- 'output': '9.0\nsqrt',
- 'line': 38,
- 'kind': 'cell'}]},
- {'slug': 'packages',
- 'title': 'Packages',
'section': 'Modules',
- 'summary': 'Packages organize modules into importable directories.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/modules.html#packages',
+ 'see_also': ['modules', 'functions'],
+ 'slug': 'import-aliases',
+ 'summary': 'as gives imported modules or names a local alias.',
+ 'title': 'Import Aliases',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'import statistics as stats\n'
+ '\n'
+ 'scores = [8, 10, 9]\n'
+ 'print(stats.mean(scores))\n'
+ 'print(stats.__name__)',
+ 'prose': 'A module alias keeps the namespace but changes the local name. Here '
+ '`stats` is shorter, but readers can still see that `mean` belongs to '
+ 'the statistics module.'},
+ {'code': 'from math import sqrt as square_root\n'
+ '\n'
+ 'print(square_root(81))\n'
+ 'print(square_root.__name__)',
+ 'prose': 'A name imported with `from` can also be aliased. Use this when the '
+ 'local name explains the role better than the original name.'}]},
+ {'cells': [{'code': 'import json\n\nprint(json.__name__)',
+ 'kind': 'cell',
+ 'line': 22,
+ 'output': 'json',
+ 'prose': ['A package is itself a module. The `json` package exposes a namespace that '
+ 'can contain submodules.']},
+ {'code': 'import json.decoder\n'
+ '\n'
+ 'print(json.decoder.__name__)\n'
+ 'print(json.decoder.JSONDecoder.__name__)',
+ 'kind': 'cell',
+ 'line': 36,
+ 'output': 'json.decoder\nJSONDecoder',
+ 'prose': ['Dotted imports name a path through a package. Importing `json.decoder` '
+ 'makes that submodule available under the package namespace.']},
+ {'code': 'import importlib\n'
+ '\n'
+ 'module = importlib.import_module("json.decoder")\n'
+ 'print(module is json.decoder)',
+ 'kind': 'cell',
+ 'line': 52,
+ 'output': 'True',
+ 'prose': ['`importlib.import_module()` imports by string. It is useful for plugin '
+ 'systems and dynamic imports, but ordinary `import` is clearer when the '
+ 'dependency is known.']},
+ {'code': 'import os\n'
+ 'import sys\n'
+ 'import tempfile\n'
+ '\n'
+ 'with tempfile.TemporaryDirectory() as tmp:\n'
+ ' pkg = os.path.join(tmp, "shapes")\n'
+ ' os.makedirs(pkg)\n'
+ ' with open(os.path.join(pkg, "__init__.py"), "w") as init:\n'
+ ' init.write("from .square import area\\n__all__ = [\'area\']\\n")\n'
+ ' with open(os.path.join(pkg, "square.py"), "w") as square:\n'
+ ' square.write("def area(side):\\n return side * side\\n")\n'
+ ' sys.path.insert(0, tmp)\n'
+ ' try:\n'
+ ' import shapes\n'
+ ' print(shapes.area(3))\n'
+ ' print(shapes.__all__)\n'
+ ' finally:\n'
+ ' sys.path.remove(tmp)\n'
+ ' sys.modules.pop("shapes", None)\n'
+ ' sys.modules.pop("shapes.square", None)',
+ 'kind': 'cell',
+ 'line': 67,
+ 'output': "9\n['area']",
+ 'prose': ["Inside a package's `__init__.py`, `from .submodule import name` re-exports "
+ "a submodule's name at the package root, and `__all__` lists the names that "
+ '`from package import *` should make visible. This cell builds a temporary '
+ '`shapes` package on disk to make both forms concrete.']}],
'code': 'import importlib\n'
'import json\n'
'import json.decoder\n'
@@ -3513,37 +7584,120 @@
'print(json.__name__)\n'
'print(json.decoder.__name__)\n'
'print(module.JSONDecoder.__name__)\n'
- 'print(module is json.decoder)\n',
- 'expected_output': 'json\njson.decoder\nJSONDecoder\nTrue\n',
+ 'print(module is json.decoder)\n'
+ '\n'
+ '\n'
+ 'import os\n'
+ 'import sys\n'
+ 'import tempfile\n'
+ '\n'
+ 'with tempfile.TemporaryDirectory() as tmp:\n'
+ ' pkg = os.path.join(tmp, "shapes")\n'
+ ' os.makedirs(pkg)\n'
+ ' with open(os.path.join(pkg, "__init__.py"), "w") as init:\n'
+ ' init.write("from .square import area\\n__all__ = [\'area\']\\n")\n'
+ ' with open(os.path.join(pkg, "square.py"), "w") as square:\n'
+ ' square.write("def area(side):\\n return side * side\\n")\n'
+ ' sys.path.insert(0, tmp)\n'
+ ' try:\n'
+ ' import shapes\n'
+ ' print(shapes.area(3))\n'
+ ' print(shapes.__all__)\n'
+ ' finally:\n'
+ ' sys.path.remove(tmp)\n'
+ ' sys.modules.pop("shapes", None)\n'
+ ' sys.modules.pop("shapes.square", None)\n',
+ 'doc_path': '/tutorial/modules.html#packages',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/modules.html#packages',
+ 'expected_output': "json\njson.decoder\nJSONDecoder\nTrue\n9\n['area']\n",
+ 'explanation': ['Packages are modules that can contain other modules. They let a project group '
+ 'related code behind dotted import paths such as `json.decoder` or '
+ '`email.message`.',
+ 'At runtime, importing a submodule gives Python a path through that package '
+ 'structure. In a project on disk, that structure is usually a directory with '
+ 'Python files and often an `__init__.py` file.',
+ 'Use packages when one module has grown into a small namespace of related '
+ 'modules. Keep module names boring and explicit so readers can tell where '
+ 'imported definitions come from.'],
+ 'min_python': None,
'notes': ['A package is a module that can contain submodules.',
'Dotted imports should mirror a meaningful project structure.',
+ 'Use `from .submodule import name` inside a package to re-export submodule names; set '
+ '`__all__` to declare the public surface.',
'Prefer ordinary imports unless the module name is truly dynamic.'],
- 'cells': [{'prose': ['A package is itself a module. The `json` package exposes a namespace that can contain '
- 'submodules.'],
- 'code': 'import json\n\nprint(json.__name__)',
- 'output': 'json',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['Dotted imports name a path through a package. Importing `json.decoder` makes that submodule '
- 'available under the package namespace.'],
- 'code': 'import json.decoder\n\nprint(json.decoder.__name__)\nprint(json.decoder.JSONDecoder.__name__)',
- 'output': 'json.decoder\nJSONDecoder',
- 'line': 31,
- 'kind': 'cell'},
- {'prose': ['`importlib.import_module()` imports by string. It is useful for plugin systems and dynamic '
- 'imports, but ordinary `import` is clearer when the dependency is known.'],
- 'code': 'import importlib\n'
- '\n'
- 'module = importlib.import_module("json.decoder")\n'
- 'print(module is json.decoder)',
- 'output': 'True',
- 'line': 47,
- 'kind': 'cell'}]},
- {'slug': 'virtual-environments',
- 'title': 'Virtual Environments',
'section': 'Modules',
- 'summary': "Virtual environments isolate a project's Python packages.",
- 'doc_url': 'https://docs.python.org/3.13/library/venv.html',
+ 'see_also': ['modules', 'import-aliases', 'virtual-environments'],
+ 'slug': 'packages',
+ 'summary': 'Packages organize modules into importable directories.',
+ 'title': 'Packages',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'import json\n\nprint(json.__name__)',
+ 'prose': 'A package is itself a module. The `json` package exposes a namespace '
+ 'that can contain submodules.'},
+ {'code': 'import json.decoder\n'
+ '\n'
+ 'print(json.decoder.__name__)\n'
+ 'print(json.decoder.JSONDecoder.__name__)',
+ 'prose': 'Dotted imports name a path through a package. Importing '
+ '`json.decoder` makes that submodule available under the package '
+ 'namespace.'},
+ {'code': 'import importlib\n'
+ '\n'
+ 'module = importlib.import_module("json.decoder")\n'
+ 'print(module is json.decoder)',
+ 'prose': '`importlib.import_module()` imports by string. It is useful for '
+ 'plugin systems and dynamic imports, but ordinary `import` is clearer '
+ 'when the dependency is known.'},
+ {'code': 'import os\n'
+ 'import sys\n'
+ 'import tempfile\n'
+ '\n'
+ 'with tempfile.TemporaryDirectory() as tmp:\n'
+ ' pkg = os.path.join(tmp, "shapes")\n'
+ ' os.makedirs(pkg)\n'
+ ' with open(os.path.join(pkg, "__init__.py"), "w") as init:\n'
+ ' init.write("from .square import area\\n__all__ = '
+ '[\'area\']\\n")\n'
+ ' with open(os.path.join(pkg, "square.py"), "w") as square:\n'
+ ' square.write("def area(side):\\n return side * side\\n")\n'
+ ' sys.path.insert(0, tmp)\n'
+ ' try:\n'
+ ' import shapes\n'
+ ' print(shapes.area(3))\n'
+ ' print(shapes.__all__)\n'
+ ' finally:\n'
+ ' sys.path.remove(tmp)\n'
+ ' sys.modules.pop("shapes", None)\n'
+ ' sys.modules.pop("shapes.square", None)',
+ 'prose': "Inside a package's `__init__.py`, `from .submodule import name` "
+ "re-exports a submodule's name at the package root, and `__all__` "
+ 'lists the names that `from package import *` should make visible. '
+ 'This cell builds a temporary `shapes` package on disk to make both '
+ 'forms concrete.'}]},
+ {'cells': [{'code': 'builder = venv.EnvBuilder(with_pip=False)\nbuilder.create(".venv")',
+ 'kind': 'unsupported',
+ 'line': 18,
+ 'output': '',
+ 'prose': ['Dynamic Workers do not provide the `venv` module or a project environment '
+ 'workflow.']},
+ {'code': 'import pathlib\n'
+ 'import tempfile\n'
+ 'import venv\n'
+ '\n'
+ 'with tempfile.TemporaryDirectory() as directory:\n'
+ ' env_path = pathlib.Path(directory) / ".venv"\n'
+ ' builder = venv.EnvBuilder(with_pip=False)\n'
+ ' builder.create(env_path)\n'
+ '\n'
+ ' print(env_path.name)\n'
+ ' print((env_path / "pyvenv.cfg").exists())',
+ 'kind': 'cell',
+ 'line': 27,
+ 'output': '.venv\nTrue',
+ 'prose': ['`venv.EnvBuilder` creates the same kind of isolated environment as `python '
+ '-m venv`. A temporary directory keeps the example from leaving project '
+ 'files behind.']}],
'code': 'import pathlib\n'
'import tempfile\n'
'import venv\n'
@@ -3555,37 +7709,108 @@
'\n'
' print(env_path.name)\n'
' print((env_path / "pyvenv.cfg").exists())\n',
+ 'doc_path': '/library/venv.html',
+ 'doc_url': 'https://docs.python.org/3.13/library/venv.html',
'expected_output': '.venv\nTrue\n',
+ 'explanation': ["Virtual environments isolate a project's Python packages. They exist so one "
+ "project can install dependencies without changing another project's "
+ 'environment.',
+ 'The usual command-line workflow is `python -m venv .venv`, but Python also '
+ 'exposes the same feature through the `venv` module. This example creates a '
+ 'temporary environment so the example cleans up after itself.',
+ 'A virtual environment changes where Python looks for installed packages. It '
+ 'does not change the language, and it is separate from package layout, imports, '
+ 'and module names.'],
+ 'min_python': None,
'notes': ['A virtual environment gives a project its own install location.',
'Inside a venv, `sys.prefix` usually differs from `sys.base_prefix`.',
'Use `python -m venv .venv` at the command line for everyday project setup.'],
- 'cells': [{'prose': ['Dynamic Workers do not provide the `venv` module or a project environment workflow.'],
- 'code': 'builder = venv.EnvBuilder(with_pip=False)\nbuilder.create(".venv")',
- 'output': '',
- 'line': 18,
- 'kind': 'unsupported'},
- {'prose': ['`venv.EnvBuilder` creates the same kind of isolated environment as `python -m venv`. A '
- 'temporary directory keeps the example from leaving project files behind.'],
- 'code': 'import pathlib\n'
- 'import tempfile\n'
- 'import venv\n'
+ 'section': 'Modules',
+ 'see_also': [],
+ 'slug': 'virtual-environments',
+ 'summary': "Virtual environments isolate a project's Python packages.",
+ 'title': 'Virtual Environments',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'import pathlib\n'
+ 'import tempfile\n'
+ 'import venv\n'
+ '\n'
+ 'with tempfile.TemporaryDirectory() as directory:\n'
+ ' env_path = pathlib.Path(directory) / ".venv"\n'
+ ' builder = venv.EnvBuilder(with_pip=False)\n'
+ ' builder.create(env_path)\n'
+ '\n'
+ ' print(env_path.name)\n'
+ ' print((env_path / "pyvenv.cfg").exists())',
+ 'prose': '`venv.EnvBuilder` creates the same kind of isolated environment as '
+ '`python -m venv`. A temporary directory keeps the example from '
+ 'leaving project files behind.'}]},
+ {'cells': [{'code': 'def total(numbers: list[int]) -> int:\n'
+ ' return sum(numbers)\n'
+ '\n'
+ 'print(total([1, 2, 3]))',
+ 'kind': 'cell',
+ 'line': 23,
+ 'output': '6',
+ 'prose': ['Type hints document expected parameter and return shapes. Python still '
+ 'runs the function normally at runtime.']},
+ {'code': 'print(total.__annotations__)',
+ 'kind': 'cell',
+ 'line': 38,
+ 'output': "{'numbers': list[int], 'return': }",
+ 'prose': ['Python stores annotations on the function object for tools and '
+ 'introspection. Type checkers use this information without changing the '
+ 'function call syntax.']},
+ {'code': 'def label(score: int) -> str:\n'
+ ' return f"score={score}"\n'
+ '\n'
+ 'print(label("high"))',
+ 'kind': 'cell',
+ 'line': 50,
+ 'output': 'score=high',
+ 'prose': ['Most hints are not runtime validation. This call passes a string where the '
+ 'hint says `int`; Python still calls the function because the body can '
+ 'format any value.']},
+ {'code': 'def find(name: str, options: list[str]) -> str | None:\n'
+ ' return name if name in options else None\n'
+ '\n'
+ 'print(find("Ada", ["Ada", "Grace"]))\n'
+ 'print(find("Guido", ["Ada", "Grace"]))\n'
'\n'
- 'with tempfile.TemporaryDirectory() as directory:\n'
- ' env_path = pathlib.Path(directory) / ".venv"\n'
- ' builder = venv.EnvBuilder(with_pip=False)\n'
- ' builder.create(env_path)\n'
'\n'
- ' print(env_path.name)\n'
- ' print((env_path / "pyvenv.cfg").exists())',
- 'output': '.venv\nTrue',
- 'line': 27,
- 'kind': 'cell'}]},
- {'slug': 'type-hints',
- 'title': 'Type Hints',
- 'section': 'Types',
- 'summary': 'Annotations document expected types and power static analysis.',
- 'doc_url': 'https://docs.python.org/3.13/library/typing.html',
- 'code': 'def total(numbers: list[int]) -> int:\n'
+ 'from typing import Optional\n'
+ '\n'
+ 'def lookup(name: str) -> Optional[int]:\n'
+ ' table = {"Ada": 1815, "Grace": 1906}\n'
+ ' return table.get(name)\n'
+ '\n'
+ 'print(lookup("Ada"))\n'
+ 'print(lookup("Guido"))',
+ 'kind': 'cell',
+ 'line': 65,
+ 'output': 'Ada\nNone\n1815\nNone',
+ 'prose': ['Use `X | Y` (PEP 604) to express "either type". `str | None` says the '
+ 'result is a string or absent. `typing.Optional[X]` is the older, '
+ 'still-supported spelling for the same idea — `Optional[X]` is equivalent '
+ 'to `X | None`.']},
+ {'code': 'from typing import TypeAlias\n'
+ '\n'
+ 'Score: TypeAlias = int\n'
+ '\n'
+ 'def grade(score: Score) -> str:\n'
+ ' return "pass" if score >= 50 else "fail"\n'
+ '\n'
+ 'print(grade(72))',
+ 'kind': 'cell',
+ 'line': 94,
+ 'output': 'pass',
+ 'prose': ['`TypeAlias` names a type so it can be reused with intent. `Score: '
+ 'TypeAlias = int` keeps the underlying type at runtime but lets the API '
+ 'talk about a domain concept rather than a primitive.']}],
+ 'code': 'from typing import TypeAlias\n'
+ '\n'
+ 'def total(numbers: list[int]) -> int:\n'
' return sum(numbers)\n'
'\n'
'print(total([1, 2, 3]))\n'
@@ -3595,34 +7820,140 @@
'def label(score: int) -> str:\n'
' return f"score={score}"\n'
'\n'
- 'print(label("high"))\n',
- 'expected_output': "6\n{'numbers': list[int], 'return': }\nscore=high\n",
+ 'print(label("high"))\n'
+ '\n'
+ '\n'
+ 'def find(name: str, options: list[str]) -> str | None:\n'
+ ' return name if name in options else None\n'
+ '\n'
+ 'print(find("Ada", ["Ada", "Grace"]))\n'
+ 'print(find("Guido", ["Ada", "Grace"]))\n'
+ '\n'
+ '\n'
+ 'from typing import Optional\n'
+ '\n'
+ 'def lookup(name: str) -> Optional[int]:\n'
+ ' table = {"Ada": 1815, "Grace": 1906}\n'
+ ' return table.get(name)\n'
+ '\n'
+ 'print(lookup("Ada"))\n'
+ 'print(lookup("Guido"))\n'
+ '\n'
+ '\n'
+ 'Score: TypeAlias = int\n'
+ '\n'
+ 'def grade(score: Score) -> str:\n'
+ ' return "pass" if score >= 50 else "fail"\n'
+ '\n'
+ 'print(grade(72))\n',
+ 'doc_path': '/library/typing.html',
+ 'doc_url': 'https://docs.python.org/3.13/library/typing.html',
+ 'expected_output': '6\n'
+ "{'numbers': list[int], 'return': }\n"
+ 'score=high\n'
+ 'Ada\n'
+ 'None\n'
+ '1815\n'
+ 'None\n'
+ 'pass\n',
+ 'explanation': ['Type hints are annotations that document expected shapes for values, '
+ 'parameters, and return results. They exist so tools and readers can understand '
+ 'API boundaries before the program runs.',
+ 'Python stores many annotations but does not enforce most of them at runtime. '
+ 'Use type hints for communication and static analysis; use validation or '
+ 'exceptions when runtime checks are required.',
+ 'The alternative to an annotation is prose, tests, or runtime validation. Good '
+ 'Python code often uses all three at important boundaries.'],
+ 'min_python': None,
'notes': ['Python does not enforce most type hints at runtime.',
'Tools like type checkers and editors use annotations to catch mistakes earlier.',
+ 'Use `X | Y` for unions and `Optional[X]` for "X or None"; both spellings mean the '
+ 'same thing.',
+ 'Reach for `TypeAlias` when a domain name reads better than a raw primitive type.',
'Use runtime validation when untrusted input must be rejected while the program runs.'],
- 'cells': [{'prose': ['Type hints document expected parameter and return shapes. Python still runs the function '
- 'normally at runtime.'],
- 'code': 'def total(numbers: list[int]) -> int:\n return sum(numbers)\n\nprint(total([1, 2, 3]))',
- 'output': '6',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['Python stores annotations on the function object for tools and introspection. Type checkers '
- 'use this information without changing the function call syntax.'],
- 'code': 'print(total.__annotations__)',
- 'output': "{'numbers': list[int], 'return': }",
- 'line': 32,
- 'kind': 'cell'},
- {'prose': ['Most hints are not runtime validation. This call passes a string where the hint says `int`; '
- 'Python still calls the function because the body can format any value.'],
- 'code': 'def label(score: int) -> str:\n return f"score={score}"\n\nprint(label("high"))',
- 'output': 'score=high',
- 'line': 44,
- 'kind': 'cell'}]},
- {'slug': 'runtime-type-checks',
- 'title': 'Runtime Type Checks',
'section': 'Types',
- 'summary': 'type, isinstance, and issubclass inspect runtime relationships.',
- 'doc_url': 'https://docs.python.org/3.13/library/functions.html#isinstance',
+ 'see_also': ['union-and-optional-types',
+ 'type-aliases',
+ 'generics-and-typevar',
+ 'runtime-type-checks'],
+ 'slug': 'type-hints',
+ 'summary': 'Annotations document expected types and power static analysis.',
+ 'title': 'Type Hints',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'def total(numbers: list[int]) -> int:\n'
+ ' return sum(numbers)\n'
+ '\n'
+ 'print(total([1, 2, 3]))',
+ 'prose': 'Type hints document expected parameter and return shapes. Python '
+ 'still runs the function normally at runtime.'},
+ {'code': 'print(total.__annotations__)',
+ 'prose': 'Python stores annotations on the function object for tools and '
+ 'introspection. Type checkers use this information without changing '
+ 'the function call syntax.'},
+ {'code': 'def label(score: int) -> str:\n'
+ ' return f"score={score}"\n'
+ '\n'
+ 'print(label("high"))',
+ 'prose': 'Most hints are not runtime validation. This call passes a string '
+ 'where the hint says `int`; Python still calls the function because '
+ 'the body can format any value.'},
+ {'code': 'def find(name: str, options: list[str]) -> str | None:\n'
+ ' return name if name in options else None\n'
+ '\n'
+ 'print(find("Ada", ["Ada", "Grace"]))\n'
+ 'print(find("Guido", ["Ada", "Grace"]))\n'
+ '\n'
+ '\n'
+ 'from typing import Optional\n'
+ '\n'
+ 'def lookup(name: str) -> Optional[int]:\n'
+ ' table = {"Ada": 1815, "Grace": 1906}\n'
+ ' return table.get(name)\n'
+ '\n'
+ 'print(lookup("Ada"))\n'
+ 'print(lookup("Guido"))',
+ 'prose': 'Use `X | Y` (PEP 604) to express "either type". `str | None` says the '
+ 'result is a string or absent. `typing.Optional[X]` is the older, '
+ 'still-supported spelling for the same idea — `Optional[X]` is '
+ 'equivalent to `X | None`.'},
+ {'code': 'from typing import TypeAlias\n'
+ '\n'
+ 'Score: TypeAlias = int\n'
+ '\n'
+ 'def grade(score: Score) -> str:\n'
+ ' return "pass" if score >= 50 else "fail"\n'
+ '\n'
+ 'print(grade(72))',
+ 'prose': '`TypeAlias` names a type so it can be reused with intent. `Score: '
+ 'TypeAlias = int` keeps the underlying type at runtime but lets the '
+ 'API talk about a domain concept rather than a primitive.'}]},
+ {'cells': [{'code': 'class Animal:\n'
+ ' pass\n'
+ '\n'
+ 'class Dog(Animal):\n'
+ ' pass\n'
+ '\n'
+ 'pet = Dog()\n'
+ 'print(type(pet).__name__)\n'
+ 'print(type(pet) is Animal)',
+ 'kind': 'cell',
+ 'line': 23,
+ 'output': 'Dog\nFalse',
+ 'prose': ['`type()` reports the exact runtime class. A `Dog` instance is not exactly '
+ 'an `Animal` instance.']},
+ {'code': 'print(isinstance(pet, Dog))\nprint(isinstance(pet, Animal))',
+ 'kind': 'cell',
+ 'line': 44,
+ 'output': 'True\nTrue',
+ 'prose': ['`isinstance()` accepts subclasses, which is usually what API boundaries '
+ 'want.']},
+ {'code': 'print(issubclass(Dog, Animal))',
+ 'kind': 'cell',
+ 'line': 58,
+ 'output': 'True',
+ 'prose': ['`issubclass()` checks class relationships rather than individual '
+ 'objects.']}],
'code': 'class Animal:\n'
' pass\n'
'\n'
@@ -3635,39 +7966,74 @@
'print(type(pet) is Animal)\n'
'print(isinstance(pet, Animal))\n'
'print(issubclass(Dog, Animal))\n',
+ 'doc_path': '/library/functions.html#isinstance',
+ 'doc_url': 'https://docs.python.org/3.13/library/functions.html#isinstance',
'expected_output': 'Dog\nFalse\nTrue\nTrue\n',
+ 'explanation': ['Runtime type checks inspect real objects while the program is running. They are '
+ 'different from type hints, which mostly guide tools before the program runs.',
+ 'Use `type()` when the exact class matters, `isinstance()` when subclasses '
+ 'should count, and `issubclass()` when checking class relationships. Most APIs '
+ 'prefer behavior over type checks, but runtime checks are useful at input '
+ 'boundaries.',
+ 'Do not turn every function into a wall of `isinstance()` calls. If the code '
+ 'only needs an object that can perform an operation, duck typing or a protocol '
+ 'may be clearer.'],
+ 'min_python': None,
'notes': ['`type()` is exact; `isinstance()` follows inheritance.',
'Runtime checks inspect objects, not static annotations.',
'Prefer behavior, protocols, or clear validation over scattered type checks.'],
- 'cells': [{'prose': ['`type()` reports the exact runtime class. A `Dog` instance is not exactly an `Animal` '
- 'instance.'],
- 'code': 'class Animal:\n'
- ' pass\n'
- '\n'
- 'class Dog(Animal):\n'
- ' pass\n'
+ 'section': 'Types',
+ 'see_also': ['type-hints', 'protocols', 'casts-and-any', 'abstract-base-classes'],
+ 'slug': 'runtime-type-checks',
+ 'summary': 'type, isinstance, and issubclass inspect runtime relationships.',
+ 'title': 'Runtime Type Checks',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'class Animal:\n'
+ ' pass\n'
+ '\n'
+ 'class Dog(Animal):\n'
+ ' pass\n'
+ '\n'
+ 'pet = Dog()\n'
+ 'print(type(pet).__name__)\n'
+ 'print(type(pet) is Animal)',
+ 'prose': '`type()` reports the exact runtime class. A `Dog` instance is not '
+ 'exactly an `Animal` instance.'},
+ {'code': 'print(isinstance(pet, Dog))\nprint(isinstance(pet, Animal))',
+ 'prose': '`isinstance()` accepts subclasses, which is usually what API '
+ 'boundaries want.'},
+ {'code': 'print(issubclass(Dog, Animal))',
+ 'prose': '`issubclass()` checks class relationships rather than individual '
+ 'objects.'}]},
+ {'cells': [{'code': 'def label(value: int | str) -> str:\n'
+ ' return f"item-{value}"\n'
'\n'
- 'pet = Dog()\n'
- 'print(type(pet).__name__)\n'
- 'print(type(pet) is Animal)',
- 'output': 'Dog\nFalse',
+ 'print(label(3))\n'
+ 'print(label("A"))',
+ 'kind': 'cell',
'line': 22,
- 'kind': 'cell'},
- {'prose': ['`isinstance()` accepts subclasses, which is usually what API boundaries want.'],
- 'code': 'print(isinstance(pet, Dog))\nprint(isinstance(pet, Animal))',
- 'output': 'True\nTrue',
- 'line': 43,
- 'kind': 'cell'},
- {'prose': ['`issubclass()` checks class relationships rather than individual objects.'],
- 'code': 'print(issubclass(Dog, Animal))',
- 'output': 'True',
- 'line': 57,
- 'kind': 'cell'}]},
- {'slug': 'union-and-optional-types',
- 'title': 'Union and Optional Types',
- 'section': 'Types',
- 'summary': 'The | operator describes values that may have more than one static type.',
- 'doc_url': 'https://docs.python.org/3.13/library/typing.html#typing.Optional',
+ 'output': 'item-3\nitem-A',
+ 'prose': ['Use `A | B` when a value may have either type. The function body should '
+ 'use operations that make sense for every member of the union.']},
+ {'code': 'def greeting(name: str | None) -> str:\n'
+ ' if name is None:\n'
+ ' return "hello guest"\n'
+ ' return f"hello {name.upper()}"\n'
+ '\n'
+ 'print(greeting(None))\n'
+ 'print(greeting("Ada"))',
+ 'kind': 'cell',
+ 'line': 39,
+ 'output': 'hello guest\nhello ADA',
+ 'prose': ['`str | None` means the function accepts either a string or explicit '
+ 'absence. Check for `None` before calling string methods.']},
+ {'code': 'print(greeting.__annotations__)',
+ 'kind': 'cell',
+ 'line': 58,
+ 'output': "{'name': str | None, 'return': }",
+ 'prose': ['Union annotations are visible at runtime, but Python does not enforce them '
+ 'when the function is called.']}],
'code': 'def label(value: int | str) -> str:\n'
' return f"item-{value}"\n'
'\n'
@@ -3682,43 +8048,77 @@
'print(greeting(None))\n'
'print(greeting("Ada"))\n'
'print(greeting.__annotations__)\n',
- 'expected_output': "item-3\nitem-A\nhello guest\nhello ADA\n{'name': str | None, 'return': }\n",
+ 'doc_path': '/library/typing.html#typing.Optional',
+ 'doc_url': 'https://docs.python.org/3.13/library/typing.html#typing.Optional',
+ 'expected_output': 'item-3\n'
+ 'item-A\n'
+ 'hello guest\n'
+ 'hello ADA\n'
+ "{'name': str | None, 'return': }\n",
+ 'explanation': ['A union type says that a value may have one of several static shapes. `int | '
+ 'str` means callers may pass either an integer or a string.',
+ '`T | None` is the modern spelling for an optional value. The annotation '
+ 'documents that absence is expected, but the code still needs to handle `None` '
+ 'before using the non-optional behavior.',
+ 'Unions are useful at boundaries where input is flexible. Inside a function, '
+ 'narrow the value with an `is None`, `isinstance()`, or pattern check so the '
+ 'rest of the code has one clear shape.'],
+ 'min_python': None,
'notes': ['Use `A | B` when a value may have either type.',
'`T | None` means absence is an expected case, not an error by itself.',
'Narrow unions before using behavior that belongs to only one member type.'],
- 'cells': [{'prose': ['Use `A | B` when a value may have either type. The function body should use operations that '
- 'make sense for every member of the union.'],
- 'code': 'def label(value: int | str) -> str:\n'
- ' return f"item-{value}"\n'
+ 'section': 'Types',
+ 'see_also': ['none', 'type-hints', 'match-statements'],
+ 'slug': 'union-and-optional-types',
+ 'summary': 'The | operator describes values that may have more than one static type.',
+ 'title': 'Union and Optional Types',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'def label(value: int | str) -> str:\n'
+ ' return f"item-{value}"\n'
+ '\n'
+ 'print(label(3))\n'
+ 'print(label("A"))',
+ 'prose': 'Use `A | B` when a value may have either type. The function body '
+ 'should use operations that make sense for every member of the union.'},
+ {'code': 'def greeting(name: str | None) -> str:\n'
+ ' if name is None:\n'
+ ' return "hello guest"\n'
+ ' return f"hello {name.upper()}"\n'
+ '\n'
+ 'print(greeting(None))\n'
+ 'print(greeting("Ada"))',
+ 'prose': '`str | None` means the function accepts either a string or explicit '
+ 'absence. Check for `None` before calling string methods.'},
+ {'code': 'print(greeting.__annotations__)',
+ 'prose': 'Union annotations are visible at runtime, but Python does not enforce '
+ 'them when the function is called.'}]},
+ {'cells': [{'code': 'type UserId = int\n'
+ 'type Scores = dict[UserId, int]\n'
'\n'
- 'print(label(3))\n'
- 'print(label("A"))',
- 'output': 'item-3\nitem-A',
- 'line': 22,
- 'kind': 'cell'},
- {'prose': ['`str | None` means the function accepts either a string or explicit absence. Check for `None` '
- 'before calling string methods.'],
- 'code': 'def greeting(name: str | None) -> str:\n'
- ' if name is None:\n'
- ' return "hello guest"\n'
- ' return f"hello {name.upper()}"\n'
'\n'
- 'print(greeting(None))\n'
- 'print(greeting("Ada"))',
- 'output': 'hello guest\nhello ADA',
- 'line': 39,
- 'kind': 'cell'},
- {'prose': ['Union annotations are visible at runtime, but Python does not enforce them when the function '
- 'is called.'],
- 'code': 'print(greeting.__annotations__)',
- 'output': "{'name': str | None, 'return': }",
- 'line': 58,
- 'kind': 'cell'}]},
- {'slug': 'type-aliases',
- 'title': 'Type Aliases',
- 'section': 'Types',
- 'summary': 'Type aliases give a meaningful name to a repeated type shape.',
- 'doc_url': 'https://docs.python.org/3.13/library/typing.html#type-aliases',
+ 'def best_user(scores: Scores) -> UserId:\n'
+ ' return max(scores, key=scores.get)\n'
+ '\n'
+ 'scores: Scores = {1: 98, 2: 91}\n'
+ 'print(best_user(scores))',
+ 'kind': 'cell',
+ 'line': 22,
+ 'output': '1',
+ 'prose': ['The `type` statement names an annotation shape. Here `Scores` means a '
+ 'dictionary from user IDs to integer scores.']},
+ {'code': 'print(UserId.__name__)\nprint(Scores.__name__)',
+ 'kind': 'cell',
+ 'line': 42,
+ 'output': 'UserId\nScores',
+ 'prose': ['Modern aliases are runtime objects that keep their alias name for '
+ 'introspection.']},
+ {'code': 'LegacyName = str\nprint(LegacyName("Ada"))\nprint(LegacyName is str)',
+ 'kind': 'cell',
+ 'line': 56,
+ 'output': 'Ada\nTrue',
+ 'prose': ['Assignment-style aliases are still common, but they are just ordinary '
+ 'names bound to existing objects.']}],
'code': 'type UserId = int\n'
'type Scores = dict[UserId, int]\n'
'LegacyName = str\n'
@@ -3731,40 +8131,82 @@
'print(best_user(scores))\n'
'print(UserId.__name__)\n'
'print(LegacyName("Ada"))\n',
+ 'doc_path': '/library/typing.html#type-aliases',
+ 'doc_url': 'https://docs.python.org/3.13/library/typing.html#type-aliases',
'expected_output': '1\nUserId\nAda\n',
+ 'explanation': ['A type alias gives a name to an annotation shape. It helps readers and type '
+ 'checkers understand the role of a value without repeating a long type '
+ 'expression everywhere.',
+ 'Python 3.13 supports the `type` statement for explicit aliases. Older '
+ 'assignment-style aliases still appear in code, but the `type` statement makes '
+ 'the intent clear and creates a `TypeAliasType` object at runtime.',
+ 'An alias does not create a new runtime type. If you need a static distinction '
+ 'between compatible values such as user IDs and order IDs, use `NewType` '
+ 'instead.'],
+ 'min_python': None,
'notes': ['Use aliases to name repeated or domain-specific annotation shapes.',
'A type alias does not validate values at runtime.',
- 'Use `NewType` when two values share a runtime representation but should not be mixed statically.'],
- 'cells': [{'prose': ['The `type` statement names an annotation shape. Here `Scores` means a dictionary from user IDs '
- 'to integer scores.'],
- 'code': 'type UserId = int\n'
- 'type Scores = dict[UserId, int]\n'
+ 'Use `NewType` when two values share a runtime representation but should not be mixed '
+ 'statically.'],
+ 'section': 'Types',
+ 'see_also': ['type-hints', 'newtype', 'union-and-optional-types'],
+ 'slug': 'type-aliases',
+ 'summary': 'Type aliases give a meaningful name to a repeated type shape.',
+ 'title': 'Type Aliases',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'type UserId = int\n'
+ 'type Scores = dict[UserId, int]\n'
+ '\n'
+ '\n'
+ 'def best_user(scores: Scores) -> UserId:\n'
+ ' return max(scores, key=scores.get)\n'
+ '\n'
+ 'scores: Scores = {1: 98, 2: 91}\n'
+ 'print(best_user(scores))',
+ 'prose': 'The `type` statement names an annotation shape. Here `Scores` means a '
+ 'dictionary from user IDs to integer scores.'},
+ {'code': 'print(UserId.__name__)\nprint(Scores.__name__)',
+ 'prose': 'Modern aliases are runtime objects that keep their alias name for '
+ 'introspection.'},
+ {'code': 'LegacyName = str\nprint(LegacyName("Ada"))\nprint(LegacyName is str)',
+ 'prose': 'Assignment-style aliases are still common, but they are just ordinary '
+ 'names bound to existing objects.'}]},
+ {'cells': [{'code': 'from typing import TypedDict\n'
'\n'
+ 'class User(TypedDict):\n'
+ ' name: str\n'
+ ' score: int\n'
'\n'
- 'def best_user(scores: Scores) -> UserId:\n'
- ' return max(scores, key=scores.get)\n'
'\n'
- 'scores: Scores = {1: 98, 2: 91}\n'
- 'print(best_user(scores))',
- 'output': '1',
- 'line': 22,
- 'kind': 'cell'},
- {'prose': ['Modern aliases are runtime objects that keep their alias name for introspection.'],
- 'code': 'print(UserId.__name__)\nprint(Scores.__name__)',
- 'output': 'UserId\nScores',
- 'line': 42,
- 'kind': 'cell'},
- {'prose': ['Assignment-style aliases are still common, but they are just ordinary names bound to existing '
- 'objects.'],
- 'code': 'LegacyName = str\nprint(LegacyName("Ada"))\nprint(LegacyName is str)',
- 'output': 'Ada\nTrue',
- 'line': 56,
- 'kind': 'cell'}]},
- {'slug': 'typed-dicts',
- 'title': 'TypedDict',
- 'section': 'Types',
- 'summary': 'TypedDict describes dictionaries with known string keys.',
- 'doc_url': 'https://docs.python.org/3.13/library/typing.html#typing.TypedDict',
+ 'def describe(user: User) -> str:\n'
+ ' return f"{user[\'name\']}: {user[\'score\']}"\n'
+ '\n'
+ 'record: User = {"name": "Ada", "score": 98}\n'
+ 'print(describe(record))',
+ 'kind': 'cell',
+ 'line': 23,
+ 'output': 'Ada: 98',
+ 'prose': ['Use `TypedDict` for JSON-like records that remain dictionaries.']},
+ {'code': 'print(isinstance(record, dict))\nprint(type(record).__name__)',
+ 'kind': 'cell',
+ 'line': 46,
+ 'output': 'True\ndict',
+ 'prose': ['At runtime, a `TypedDict` value is still a plain dictionary.']},
+ {'code': 'from typing import NotRequired\n'
+ '\n'
+ 'class UserWithNickname(TypedDict):\n'
+ ' name: str\n'
+ ' score: int\n'
+ ' nickname: NotRequired[str]\n'
+ '\n'
+ 'record: UserWithNickname = {"name": "Ada", "score": 98}\n'
+ 'print(record.get("nickname", "none"))',
+ 'kind': 'cell',
+ 'line': 60,
+ 'output': 'none',
+ 'prose': ['`NotRequired` marks a key that type checkers should treat as optional. '
+ 'Runtime lookup still uses normal dictionary tools such as `get()`.']}],
'code': 'from typing import NotRequired, TypedDict\n'
'\n'
'class User(TypedDict):\n'
@@ -3780,50 +8222,255 @@
'print(describe(record))\n'
'print(isinstance(record, dict))\n'
'print(record.get("nickname", "none"))\n',
+ 'doc_path': '/library/typing.html#typing.TypedDict',
+ 'doc_url': 'https://docs.python.org/3.13/library/typing.html#typing.TypedDict',
'expected_output': 'Ada: 98\nTrue\nnone\n',
+ 'explanation': ['`TypedDict` describes dictionary records with known keys. It is useful for '
+ 'JSON-like data that should remain a dictionary instead of becoming a class '
+ 'instance.',
+ 'The important boundary is static versus runtime behavior. Type checkers can '
+ 'know that `name` is a string and `score` is an integer, but at runtime the '
+ 'value is still an ordinary `dict`.',
+ 'Use `TypedDict` for external records and `dataclass` when your own program '
+ 'wants attribute access, methods, and construction behavior.'],
+ 'min_python': None,
'notes': ['Use `TypedDict` for dictionary records from JSON or APIs.',
'Type checkers understand required and optional keys.',
'Runtime behavior is still ordinary dictionary behavior.'],
- 'cells': [{'prose': ['Use `TypedDict` for JSON-like records that remain dictionaries.'],
- 'code': 'from typing import TypedDict\n'
+ 'section': 'Types',
+ 'see_also': ['dicts', 'json', 'dataclasses', 'structured-data-shapes'],
+ 'slug': 'typed-dicts',
+ 'summary': 'TypedDict describes dictionaries with known string keys.',
+ 'title': 'TypedDict',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'from typing import TypedDict\n'
+ '\n'
+ 'class User(TypedDict):\n'
+ ' name: str\n'
+ ' score: int\n'
+ '\n'
+ '\n'
+ 'def describe(user: User) -> str:\n'
+ ' return f"{user[\'name\']}: {user[\'score\']}"\n'
+ '\n'
+ 'record: User = {"name": "Ada", "score": 98}\n'
+ 'print(describe(record))',
+ 'prose': 'Use `TypedDict` for JSON-like records that remain dictionaries.'},
+ {'code': 'print(isinstance(record, dict))\nprint(type(record).__name__)',
+ 'prose': 'At runtime, a `TypedDict` value is still a plain dictionary.'},
+ {'code': 'from typing import NotRequired\n'
+ '\n'
+ 'class UserWithNickname(TypedDict):\n'
+ ' name: str\n'
+ ' score: int\n'
+ ' nickname: NotRequired[str]\n'
+ '\n'
+ 'record: UserWithNickname = {"name": "Ada", "score": 98}\n'
+ 'print(record.get("nickname", "none"))',
+ 'prose': '`NotRequired` marks a key that type checkers should treat as '
+ 'optional. Runtime lookup still uses normal dictionary tools such as '
+ '`get()`.'}]},
+ {'cells': [{'code': 'from dataclasses import dataclass\n'
'\n'
- 'class User(TypedDict):\n'
+ '@dataclass\n'
+ 'class UserClass:\n'
' name: str\n'
' score: int\n'
'\n'
+ 'a = UserClass("Ada", 98)\n'
+ 'print(a)\n'
+ 'a.score = 100\n'
+ 'print(a.score)',
+ 'kind': 'cell',
+ 'line': 23,
+ 'output': "UserClass(name='Ada', score=98)\n100",
+ 'prose': ['A dataclass is a normal class with `__init__` and `__repr__` generated '
+ 'from the annotated fields. Instances are mutable, support attribute '
+ 'access, and can carry methods like any other class.']},
+ {'code': 'from typing import NamedTuple\n'
'\n'
- 'def describe(user: User) -> str:\n'
- ' return f"{user[\'name\']}: {user[\'score\']}"\n'
- '\n'
- 'record: User = {"name": "Ada", "score": 98}\n'
- 'print(describe(record))',
- 'output': 'Ada: 98',
- 'line': 22,
- 'kind': 'cell'},
- {'prose': ['At runtime, a `TypedDict` value is still a plain dictionary.'],
- 'code': 'print(isinstance(record, dict))\nprint(type(record).__name__)',
- 'output': 'True\ndict',
- 'line': 45,
- 'kind': 'cell'},
- {'prose': ['`NotRequired` marks a key that type checkers should treat as optional. Runtime lookup still '
- 'uses normal dictionary tools such as `get()`.'],
- 'code': 'from typing import NotRequired\n'
+ 'class UserTuple(NamedTuple):\n'
+ ' name: str\n'
+ ' score: int\n'
'\n'
- 'class UserWithNickname(TypedDict):\n'
+ 'b = UserTuple("Ada", 98)\n'
+ 'print(b)\n'
+ 'print(b.name, b[1])\n'
+ 'print(b._replace(score=100))',
+ 'kind': 'cell',
+ 'line': 46,
+ 'output': "UserTuple(name='Ada', score=98)\nAda 98\nUserTuple(name='Ada', score=100)",
+ 'prose': ['A `NamedTuple` is a tuple subclass with named positions. Instances are '
+ 'immutable, support both `obj.field` and `obj[index]`, and the helper '
+ '`_replace` produces a modified copy without mutating the original (since '
+ 'assigning to a field would fail).']},
+ {'code': 'from typing import TypedDict\n'
+ '\n'
+ 'class UserDict(TypedDict):\n'
' name: str\n'
' score: int\n'
- ' nickname: NotRequired[str]\n'
'\n'
- 'record: UserWithNickname = {"name": "Ada", "score": 98}\n'
- 'print(record.get("nickname", "none"))',
- 'output': 'none',
- 'line': 59,
- 'kind': 'cell'}]},
- {'slug': 'literal-and-final',
- 'title': 'Literal and Final',
- 'section': 'Types',
- 'summary': 'Literal restricts values, while Final marks names that should not be rebound.',
- 'doc_url': 'https://docs.python.org/3.13/library/typing.html#typing.Literal',
+ 'c: UserDict = {"name": "Ada", "score": 98}\n'
+ 'print(c)\n'
+ 'print(c["name"])\n'
+ 'print(type(c).__name__)',
+ 'kind': 'cell',
+ 'line': 69,
+ 'output': "{'name': 'Ada', 'score': 98}\nAda\ndict",
+ 'prose': ['A `TypedDict` is a plain dictionary at runtime. The annotations exist only '
+ 'for the type checker, so the value behaves like any `dict` — useful for '
+ 'JSON-shaped data that crosses an API boundary as a mapping.']},
+ {'code': 'print(isinstance(a, UserClass))\n'
+ 'print(isinstance(b, tuple))\n'
+ 'print(isinstance(c, dict))',
+ 'kind': 'cell',
+ 'line': 92,
+ 'output': 'True\nTrue\nTrue',
+ 'prose': ['Same record, three runtime identities. The dataclass is its own class. The '
+ '`NamedTuple` is literally a tuple. The `TypedDict` is literally a dict. '
+ 'That difference drives the choice: pick the form whose runtime behavior '
+ 'matches what the rest of the program already expects.']}],
+ 'code': 'from dataclasses import dataclass\n'
+ 'from typing import NamedTuple, TypedDict\n'
+ '\n'
+ '@dataclass\n'
+ 'class UserClass:\n'
+ ' name: str\n'
+ ' score: int\n'
+ '\n'
+ 'class UserTuple(NamedTuple):\n'
+ ' name: str\n'
+ ' score: int\n'
+ '\n'
+ 'class UserDict(TypedDict):\n'
+ ' name: str\n'
+ ' score: int\n'
+ '\n'
+ 'a = UserClass("Ada", 98)\n'
+ 'print(a)\n'
+ 'a.score = 100\n'
+ 'print(a.score)\n'
+ '\n'
+ 'b = UserTuple("Ada", 98)\n'
+ 'print(b)\n'
+ 'print(b.name, b[1])\n'
+ 'print(b._replace(score=100))\n'
+ '\n'
+ 'c: UserDict = {"name": "Ada", "score": 98}\n'
+ 'print(c)\n'
+ 'print(c["name"])\n'
+ 'print(type(c).__name__)\n'
+ '\n'
+ 'print(isinstance(a, UserClass))\n'
+ 'print(isinstance(b, tuple))\n'
+ 'print(isinstance(c, dict))\n',
+ 'doc_path': '/library/dataclasses.html',
+ 'doc_url': 'https://docs.python.org/3.13/library/dataclasses.html',
+ 'expected_output': "UserClass(name='Ada', score=98)\n"
+ '100\n'
+ "UserTuple(name='Ada', score=98)\n"
+ 'Ada 98\n'
+ "UserTuple(name='Ada', score=100)\n"
+ "{'name': 'Ada', 'score': 98}\n"
+ 'Ada\n'
+ 'dict\n'
+ 'True\n'
+ 'True\n'
+ 'True\n',
+ 'explanation': ['`@dataclass`, `typing.NamedTuple`, and `typing.TypedDict` are three ways to '
+ 'give a record a name and a schema. They model the same data but differ in '
+ 'mutability, access syntax, and what the type information costs at runtime.',
+ 'A dataclass is a regular class with `__init__` and `__repr__` generated for '
+ 'you, so instances are mutable and attribute-accessed. A `NamedTuple` is a tuple '
+ 'subclass with named positions, so instances are immutable and support both '
+ '`obj.field` and `obj[index]`. A `TypedDict` is a plain dict at runtime; the '
+ 'schema lives only in the type checker.',
+ 'Pick the shape that matches the problem: a dataclass when methods or mutability '
+ 'help; a `NamedTuple` for small immutable records that benefit from unpacking; a '
+ '`TypedDict` for JSON-shaped data that should stay as a dict at the boundary.'],
+ 'min_python': None,
+ 'notes': ['`@dataclass` — mutable, attribute access, methods; good default when behavior travels '
+ 'with data.',
+ '`typing.NamedTuple` — immutable, attribute + index access, tuple semantics; good for '
+ 'small records that flow through unpacking.',
+ '`typing.TypedDict` — runtime is `dict`, schema is type-checker-only; good for '
+ 'JSON-shaped data.',
+ '`collections.namedtuple` is the older, untyped form of `NamedTuple`; prefer the '
+ '`typing` version in new code.'],
+ 'section': 'Classes',
+ 'see_also': ['dataclasses', 'typed-dicts', 'tuples', 'classes'],
+ 'slug': 'structured-data-shapes',
+ 'summary': 'dataclass, NamedTuple, and TypedDict each model records with different trade-offs.',
+ 'title': 'Structured Data Shapes',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'from dataclasses import dataclass\n'
+ '\n'
+ '@dataclass\n'
+ 'class UserClass:\n'
+ ' name: str\n'
+ ' score: int\n'
+ '\n'
+ 'a = UserClass("Ada", 98)\n'
+ 'print(a)\n'
+ 'a.score = 100\n'
+ 'print(a.score)',
+ 'prose': 'A dataclass is a normal class with `__init__` and `__repr__` '
+ 'generated from the annotated fields. Instances are mutable, support '
+ 'attribute access, and can carry methods like any other class.'},
+ {'code': 'from typing import NamedTuple\n'
+ '\n'
+ 'class UserTuple(NamedTuple):\n'
+ ' name: str\n'
+ ' score: int\n'
+ '\n'
+ 'b = UserTuple("Ada", 98)\n'
+ 'print(b)\n'
+ 'print(b.name, b[1])\n'
+ 'print(b._replace(score=100))',
+ 'prose': 'A `NamedTuple` is a tuple subclass with named positions. Instances '
+ 'are immutable, support both `obj.field` and `obj[index]`, and the '
+ 'helper `_replace` produces a modified copy without mutating the '
+ 'original (since assigning to a field would fail).'},
+ {'code': 'from typing import TypedDict\n'
+ '\n'
+ 'class UserDict(TypedDict):\n'
+ ' name: str\n'
+ ' score: int\n'
+ '\n'
+ 'c: UserDict = {"name": "Ada", "score": 98}\n'
+ 'print(c)\n'
+ 'print(c["name"])\n'
+ 'print(type(c).__name__)',
+ 'prose': 'A `TypedDict` is a plain dictionary at runtime. The annotations exist '
+ 'only for the type checker, so the value behaves like any `dict` — '
+ 'useful for JSON-shaped data that crosses an API boundary as a '
+ 'mapping.'},
+ {'code': 'print(isinstance(a, UserClass))\n'
+ 'print(isinstance(b, tuple))\n'
+ 'print(isinstance(c, dict))',
+ 'prose': 'Same record, three runtime identities. The dataclass is its own '
+ 'class. The `NamedTuple` is literally a tuple. The `TypedDict` is '
+ 'literally a dict. That difference drives the choice: pick the form '
+ 'whose runtime behavior matches what the rest of the program already '
+ 'expects.'}]},
+ {'cells': [{'code': 'from typing import Final, Literal\n'
+ '\n'
+ 'Mode = Literal["read", "write"]\n'
+ 'DEFAULT_MODE: Final[Mode] = "read"\n'
+ '\n'
+ '\n'
+ 'def open_label(mode: Mode) -> str:\n'
+ ' return f"opening for {mode}"\n'
+ '\n'
+ 'print(open_label(DEFAULT_MODE))\n'
+ 'print(open_label("write"))\n'
+ 'print(DEFAULT_MODE)',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': 'opening for read\nopening for write\nread',
+ 'prose': ['`Literal` describes a small set of exact allowed values.']}],
'code': 'from typing import Final, Literal\n'
'\n'
'Mode = Literal["read", "write"]\n'
@@ -3836,31 +8483,76 @@
'print(open_label(DEFAULT_MODE))\n'
'print(open_label("write"))\n'
'print(DEFAULT_MODE)\n',
+ 'doc_path': '/library/typing.html#typing.Literal',
+ 'doc_url': 'https://docs.python.org/3.13/library/typing.html#typing.Literal',
'expected_output': 'opening for read\nopening for write\nread\n',
+ 'explanation': ['`Literal` restricts a value to one of a small set of exact options, and `Final` '
+ 'tells the type checker that a name should not be rebound. Both are static '
+ "promises that type checkers enforce; Python's runtime assignment rules still "
+ 'permit any value through if a program ignores the annotation.',
+ 'Use them when an annotation makes a constant or a small option set explicit at '
+ 'the API boundary. Prefer simpler neighboring tools when the extra machinery '
+ 'would hide the intent.',
+ '`Literal` pairs naturally with type aliases and overloads when a function '
+ 'should accept only a known set of strings or numbers. `Final` is most useful '
+ 'for module-level constants and class attributes that the rest of the codebase '
+ 'should treat as immutable.'],
+ 'min_python': None,
'notes': ['`Literal` describes a small set of exact allowed values.',
'`Final` tells type checkers that a name should not be rebound.',
'Both are static promises; ordinary runtime assignment rules still apply.'],
- 'cells': [{'prose': ['`Literal` describes a small set of exact allowed values.'],
- 'code': 'from typing import Final, Literal\n'
+ 'section': 'Types',
+ 'see_also': [],
+ 'slug': 'literal-and-final',
+ 'summary': 'Literal restricts values, while Final marks names that should not be rebound.',
+ 'title': 'Literal and Final',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'from typing import Final, Literal\n'
+ '\n'
+ 'Mode = Literal["read", "write"]\n'
+ 'DEFAULT_MODE: Final[Mode] = "read"\n'
+ '\n'
+ '\n'
+ 'def open_label(mode: Mode) -> str:\n'
+ ' return f"opening for {mode}"\n'
+ '\n'
+ 'print(open_label(DEFAULT_MODE))\n'
+ 'print(open_label("write"))\n'
+ 'print(DEFAULT_MODE)',
+ 'prose': '`Literal` describes a small set of exact allowed values.'}]},
+ {'cells': [{'code': 'from collections.abc import Callable\n'
'\n'
- 'Mode = Literal["read", "write"]\n'
- 'DEFAULT_MODE: Final[Mode] = "read"\n'
'\n'
+ 'def apply_twice(value: int, func: Callable[[int], int]) -> int:\n'
+ ' return func(func(value))\n'
'\n'
- 'def open_label(mode: Mode) -> str:\n'
- ' return f"opening for {mode}"\n'
'\n'
- 'print(open_label(DEFAULT_MODE))\n'
- 'print(open_label("write"))\n'
- 'print(DEFAULT_MODE)',
- 'output': 'opening for read\nopening for write\nread',
- 'line': 17,
- 'kind': 'cell'}]},
- {'slug': 'callable-types',
- 'title': 'Callable Types',
- 'section': 'Types',
- 'summary': 'Callable annotations describe functions passed as values.',
- 'doc_url': 'https://docs.python.org/3.13/library/typing.html#annotating-callable-objects',
+ 'def add_one(number: int) -> int:\n'
+ ' return number + 1\n'
+ '\n'
+ 'print(apply_twice(3, add_one))',
+ 'kind': 'cell',
+ 'line': 22,
+ 'output': '5',
+ 'prose': ['Use `Callable[[Arg], Return]` for function-shaped values. The callback is '
+ 'passed in and called by the receiving function.']},
+ {'code': 'class Doubler:\n'
+ ' def __call__(self, number: int) -> int:\n'
+ ' return number * 2\n'
+ '\n'
+ 'print(apply_twice(3, Doubler()))',
+ 'kind': 'cell',
+ 'line': 44,
+ 'output': '12',
+ 'prose': ['Callable annotations are structural: an object with `__call__` can also '
+ 'satisfy the shape.']},
+ {'code': 'print(callable(add_one), callable(Doubler()))',
+ 'kind': 'cell',
+ 'line': 60,
+ 'output': 'True True',
+ 'prose': ['Runtime callability is a separate question from static annotation. '
+ '`callable()` checks whether Python can call the object.']}],
'code': 'from collections.abc import Callable\n'
'\n'
'\n'
@@ -3878,46 +8570,81 @@
'print(apply_twice(3, add_one))\n'
'print(apply_twice(3, Doubler()))\n'
'print(callable(add_one), callable(Doubler()))\n',
+ 'doc_path': '/library/typing.html#annotating-callable-objects',
+ 'doc_url': 'https://docs.python.org/3.13/library/typing.html#annotating-callable-objects',
'expected_output': '5\n12\nTrue True\n',
+ 'explanation': ['Callable annotations describe values that can be called like functions. They '
+ 'are useful when a function accepts a callback, strategy, predicate, or '
+ 'transformation.',
+ '`Callable[[int], int]` says how the callback will be called: one integer '
+ 'argument, integer result. The annotation helps tools and readers, while runtime '
+ 'still only needs an object that is actually callable.',
+ 'Use `Callable` for simple call shapes. Use a protocol when the callback needs '
+ 'named attributes, overloaded signatures, or a more descriptive interface.'],
+ 'min_python': None,
'notes': ['Use `Callable[[Arg], Return]` for simple function-shaped values.',
'The annotation documents how the callback will be called.',
'For complex call signatures, protocols can be clearer.'],
- 'cells': [{'prose': ['Use `Callable[[Arg], Return]` for function-shaped values. The callback is passed in and called '
- 'by the receiving function.'],
- 'code': 'from collections.abc import Callable\n'
- '\n'
+ 'section': 'Types',
+ 'see_also': ['functions', 'callable-objects', 'protocols'],
+ 'slug': 'callable-types',
+ 'summary': 'Callable annotations describe functions passed as values.',
+ 'title': 'Callable Types',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'from collections.abc import Callable\n'
+ '\n'
+ '\n'
+ 'def apply_twice(value: int, func: Callable[[int], int]) -> int:\n'
+ ' return func(func(value))\n'
+ '\n'
+ '\n'
+ 'def add_one(number: int) -> int:\n'
+ ' return number + 1\n'
+ '\n'
+ 'print(apply_twice(3, add_one))',
+ 'prose': 'Use `Callable[[Arg], Return]` for function-shaped values. The '
+ 'callback is passed in and called by the receiving function.'},
+ {'code': 'class Doubler:\n'
+ ' def __call__(self, number: int) -> int:\n'
+ ' return number * 2\n'
+ '\n'
+ 'print(apply_twice(3, Doubler()))',
+ 'prose': 'Callable annotations are structural: an object with `__call__` can '
+ 'also satisfy the shape.'},
+ {'code': 'print(callable(add_one), callable(Doubler()))',
+ 'prose': 'Runtime callability is a separate question from static annotation. '
+ '`callable()` checks whether Python can call the object.'}]},
+ {'cells': [{'code': 'from typing import TypeVar\n'
'\n'
- 'def apply_twice(value: int, func: Callable[[int], int]) -> int:\n'
- ' return func(func(value))\n'
+ 'T = TypeVar("T")\n'
'\n'
'\n'
- 'def add_one(number: int) -> int:\n'
- ' return number + 1\n'
+ 'def first(items: list[T]) -> T:\n'
+ ' return items[0]\n'
'\n'
- 'print(apply_twice(3, add_one))',
- 'output': '5',
+ 'print(first([1, 2, 3]))\n'
+ 'print(first(["Ada", "Grace"]))',
+ 'kind': 'cell',
'line': 22,
- 'kind': 'cell'},
- {'prose': ['Callable annotations are structural: an object with `__call__` can also satisfy the shape.'],
- 'code': 'class Doubler:\n'
- ' def __call__(self, number: int) -> int:\n'
- ' return number * 2\n'
+ 'output': '1\nAda',
+ 'prose': ['A `TypeVar` stands for a type chosen by the caller. The return type '
+ 'follows the list element type.']},
+ {'code': 'def pair(left: T, right: T) -> tuple[T, T]:\n'
+ ' return (left, right)\n'
'\n'
- 'print(apply_twice(3, Doubler()))',
- 'output': '12',
+ 'print(pair("x", "y"))',
+ 'kind': 'cell',
'line': 44,
- 'kind': 'cell'},
- {'prose': ['Runtime callability is a separate question from static annotation. `callable()` checks whether '
- 'Python can call the object.'],
- 'code': 'print(callable(add_one), callable(Doubler()))',
- 'output': 'True True',
- 'line': 60,
- 'kind': 'cell'}]},
- {'slug': 'generics-and-typevar',
- 'title': 'Generics and TypeVar',
- 'section': 'Types',
- 'summary': 'Generics preserve type information across reusable functions and classes.',
- 'doc_url': 'https://docs.python.org/3.13/library/typing.html#generics',
+ 'output': "('x', 'y')",
+ 'prose': ['Reusing the same `TypeVar` expresses a relationship between parameters and '
+ 'results.']},
+ {'code': 'print(T.__name__)\nprint(first.__annotations__)',
+ 'kind': 'cell',
+ 'line': 59,
+ 'output': "T\n{'items': list[~T], 'return': ~T}",
+ 'prose': ['`TypeVar` is visible at runtime, but the relationship is mainly for type '
+ 'checkers.']}],
'code': 'from typing import TypeVar\n'
'\n'
'T = TypeVar("T")\n'
@@ -3934,40 +8661,71 @@
'print(first(["Ada", "Grace"]))\n'
'print(pair("x", "y"))\n'
'print(T.__name__)\n',
+ 'doc_path': '/library/typing.html#generics',
+ 'doc_url': 'https://docs.python.org/3.13/library/typing.html#generics',
'expected_output': "1\nAda\n('x', 'y')\nT\n",
+ 'explanation': ['Generics connect types across an API. A plain function that returns `object` '
+ 'loses information; a generic function can say that the returned value has the '
+ 'same type as the input element.',
+ 'A `TypeVar` stands for a type chosen by the caller. In `list[T] -> T`, the same '
+ '`T` says that a list of strings produces a string and a list of integers '
+ 'produces an integer.',
+ 'Use generics when a function or class is reusable but still preserves a '
+ 'relationship between input and output types.'],
+ 'min_python': None,
'notes': ['A `TypeVar` stands for a type chosen by the caller.',
'Generic functions avoid losing information to `object` or `Any`.',
'Use generics when input and output types are connected.'],
- 'cells': [{'prose': ['A `TypeVar` stands for a type chosen by the caller. The return type follows the list element '
- 'type.'],
- 'code': 'from typing import TypeVar\n'
+ 'section': 'Types',
+ 'see_also': ['type-hints', 'collections-module', 'casts-and-any'],
+ 'slug': 'generics-and-typevar',
+ 'summary': 'Generics preserve type information across reusable functions and classes.',
+ 'title': 'Generics and TypeVar',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'from typing import TypeVar\n'
+ '\n'
+ 'T = TypeVar("T")\n'
+ '\n'
+ '\n'
+ 'def first(items: list[T]) -> T:\n'
+ ' return items[0]\n'
+ '\n'
+ 'print(first([1, 2, 3]))\n'
+ 'print(first(["Ada", "Grace"]))',
+ 'prose': 'A `TypeVar` stands for a type chosen by the caller. The return type '
+ 'follows the list element type.'},
+ {'code': 'def pair(left: T, right: T) -> tuple[T, T]:\n'
+ ' return (left, right)\n'
+ '\n'
+ 'print(pair("x", "y"))',
+ 'prose': 'Reusing the same `TypeVar` expresses a relationship between '
+ 'parameters and results.'},
+ {'code': 'print(T.__name__)\nprint(first.__annotations__)',
+ 'prose': '`TypeVar` is visible at runtime, but the relationship is mainly for '
+ 'type checkers.'}]},
+ {'cells': [{'code': 'from collections.abc import Callable\n'
+ 'from typing import ParamSpec, TypeVar\n'
'\n'
- 'T = TypeVar("T")\n'
+ 'P = ParamSpec("P")\n'
+ 'R = TypeVar("R")\n'
'\n'
'\n'
- 'def first(items: list[T]) -> T:\n'
- ' return items[0]\n'
+ 'def logged(func: Callable[P, R]) -> Callable[P, R]:\n'
+ ' def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:\n'
+ ' print("calling", func.__name__)\n'
+ ' return func(*args, **kwargs)\n'
+ ' return wrapper\n'
'\n'
- 'print(first([1, 2, 3]))\n'
- 'print(first(["Ada", "Grace"]))',
- 'output': '1\nAda',
- 'line': 22,
- 'kind': 'cell'},
- {'prose': ['Reusing the same `TypeVar` expresses a relationship between parameters and results.'],
- 'code': 'def pair(left: T, right: T) -> tuple[T, T]:\n return (left, right)\n\nprint(pair("x", "y"))',
- 'output': "('x', 'y')",
- 'line': 44,
- 'kind': 'cell'},
- {'prose': ['`TypeVar` is visible at runtime, but the relationship is mainly for type checkers.'],
- 'code': 'print(T.__name__)\nprint(first.__annotations__)',
- 'output': "T\n{'items': list[~T], 'return': ~T}",
- 'line': 59,
- 'kind': 'cell'}]},
- {'slug': 'paramspec',
- 'title': 'ParamSpec',
- 'section': 'Types',
- 'summary': 'ParamSpec preserves callable parameter types through wrappers.',
- 'doc_url': 'https://docs.python.org/3.13/library/typing.html#typing.ParamSpec',
+ '@logged\n'
+ 'def add(left: int, right: int) -> int:\n'
+ ' return left + right\n'
+ '\n'
+ 'print(add(2, 3))',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': 'calling add\n5',
+ 'prose': ['`ParamSpec` captures the parameters of a callable.']}],
'code': 'from collections.abc import Callable\n'
'from typing import ParamSpec, TypeVar\n'
'\n'
@@ -3986,37 +8744,69 @@
' return left + right\n'
'\n'
'print(add(2, 3))\n',
+ 'doc_path': '/library/typing.html#typing.ParamSpec',
+ 'doc_url': 'https://docs.python.org/3.13/library/typing.html#typing.ParamSpec',
'expected_output': 'calling add\n5\n',
+ 'explanation': ['`ParamSpec` lets a wrapper preserve the parameter types of the function it '
+ 'wraps. The pressure that justifies it is decorators: a generic decorator that '
+ "returns `Callable[..., R]` erases the wrapped function's argument types, so "
+ 'callers lose type-checker help on every call.',
+ 'Use `ParamSpec` when a decorator should be transparent to type checkers — the '
+ 'wrapped function and the decorated name should accept the same arguments. Reach '
+ 'for plain `Callable` when the wrapper deliberately changes the signature.',
+ '`P.args` and `P.kwargs` annotate the `*args` and `**kwargs` of the inner '
+ 'wrapper, which is how the parameter spec gets bound. Pair `ParamSpec` with a '
+ '`TypeVar` for the return type when the wrapper should also stay generic over '
+ 'what the wrapped function returns.'],
+ 'min_python': None,
'notes': ['`ParamSpec` captures the parameters of a callable.',
- 'Wrappers can forward `*args` and `**kwargs` without erasing the original signature for type checkers.',
+ 'Wrappers can forward `*args` and `**kwargs` without erasing the original signature '
+ 'for type checkers.',
'This matters most for decorators.'],
- 'cells': [{'prose': ['`ParamSpec` captures the parameters of a callable.'],
- 'code': 'from collections.abc import Callable\n'
- 'from typing import ParamSpec, TypeVar\n'
- '\n'
- 'P = ParamSpec("P")\n'
- 'R = TypeVar("R")\n'
+ 'section': 'Types',
+ 'see_also': [],
+ 'slug': 'paramspec',
+ 'summary': 'ParamSpec preserves callable parameter types through wrappers.',
+ 'title': 'ParamSpec',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'from collections.abc import Callable\n'
+ 'from typing import ParamSpec, TypeVar\n'
+ '\n'
+ 'P = ParamSpec("P")\n'
+ 'R = TypeVar("R")\n'
+ '\n'
+ '\n'
+ 'def logged(func: Callable[P, R]) -> Callable[P, R]:\n'
+ ' def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:\n'
+ ' print("calling", func.__name__)\n'
+ ' return func(*args, **kwargs)\n'
+ ' return wrapper\n'
+ '\n'
+ '@logged\n'
+ 'def add(left: int, right: int) -> int:\n'
+ ' return left + right\n'
+ '\n'
+ 'print(add(2, 3))',
+ 'prose': '`ParamSpec` captures the parameters of a callable.'}]},
+ {'cells': [{'code': 'from typing import overload\n'
'\n'
+ '@overload\n'
+ 'def double(value: int) -> int: ...\n'
'\n'
- 'def logged(func: Callable[P, R]) -> Callable[P, R]:\n'
- ' def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:\n'
- ' print("calling", func.__name__)\n'
- ' return func(*args, **kwargs)\n'
- ' return wrapper\n'
+ '@overload\n'
+ 'def double(value: str) -> str: ...\n'
'\n'
- '@logged\n'
- 'def add(left: int, right: int) -> int:\n'
- ' return left + right\n'
+ 'def double(value):\n'
+ ' return value * 2\n'
'\n'
- 'print(add(2, 3))',
- 'output': 'calling add\n5',
+ 'print(double(4))\n'
+ 'print(double("ha"))\n'
+ 'print(double.__name__)',
+ 'kind': 'cell',
'line': 17,
- 'kind': 'cell'}]},
- {'slug': 'overloads',
- 'title': 'Overloads',
- 'section': 'Types',
- 'summary': 'overload describes APIs whose return type depends on argument types.',
- 'doc_url': 'https://docs.python.org/3.13/library/typing.html#typing.overload',
+ 'output': '8\nhaha\ndouble',
+ 'prose': ['`@overload` signatures are for static type checkers.']}],
'code': 'from typing import overload\n'
'\n'
'@overload\n'
@@ -4031,33 +8821,69 @@
'print(double(4))\n'
'print(double("ha"))\n'
'print(double.__name__)\n',
+ 'doc_path': '/library/typing.html#typing.overload',
+ 'doc_url': 'https://docs.python.org/3.13/library/typing.html#typing.overload',
'expected_output': '8\nhaha\ndouble\n',
+ 'explanation': ['overload describes APIs whose return type depends on argument types. It exists '
+ 'to make a common boundary explicit instead of leaving the behavior implicit in '
+ 'a larger program.',
+ 'Use it when the problem shape matches the example, and prefer simpler '
+ 'neighboring tools when the extra machinery would hide the intent. The notes '
+ 'call out the boundary so the feature stays practical rather than decorative.',
+ 'The example is small, deterministic, and focused on the semantic point. The '
+ 'complete source is editable below, while the walkthrough pairs the source with '
+ 'its output.'],
+ 'min_python': None,
'notes': ['`@overload` signatures are for static type checkers.',
'The real implementation comes after the overload declarations.',
'Use overloads when a single runtime function has multiple precise static shapes.'],
- 'cells': [{'prose': ['`@overload` signatures are for static type checkers.'],
- 'code': 'from typing import overload\n'
- '\n'
- '@overload\n'
- 'def double(value: int) -> int: ...\n'
- '\n'
- '@overload\n'
- 'def double(value: str) -> str: ...\n'
- '\n'
- 'def double(value):\n'
- ' return value * 2\n'
- '\n'
- 'print(double(4))\n'
- 'print(double("ha"))\n'
- 'print(double.__name__)',
- 'output': '8\nhaha\ndouble',
- 'line': 17,
- 'kind': 'cell'}]},
- {'slug': 'casts-and-any',
- 'title': 'Casts and Any',
'section': 'Types',
- 'summary': 'Any and cast are escape hatches for places static analysis cannot prove.',
- 'doc_url': 'https://docs.python.org/3.13/library/typing.html#typing.cast',
+ 'see_also': [],
+ 'slug': 'overloads',
+ 'summary': 'overload describes APIs whose return type depends on argument types.',
+ 'title': 'Overloads',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'from typing import overload\n'
+ '\n'
+ '@overload\n'
+ 'def double(value: int) -> int: ...\n'
+ '\n'
+ '@overload\n'
+ 'def double(value: str) -> str: ...\n'
+ '\n'
+ 'def double(value):\n'
+ ' return value * 2\n'
+ '\n'
+ 'print(double(4))\n'
+ 'print(double("ha"))\n'
+ 'print(double.__name__)',
+ 'prose': '`@overload` signatures are for static type checkers.'}]},
+ {'cells': [{'code': 'from typing import Any, cast\n'
+ '\n'
+ 'raw: Any = {"score": "98"}\n'
+ 'score_text = cast(dict[str, str], raw)["score"]\n'
+ 'score = int(score_text)\n'
+ 'print(score + 2)',
+ 'kind': 'cell',
+ 'line': 22,
+ 'output': '100',
+ 'prose': ['`Any` disables most static checking for a value. The runtime object is '
+ 'still whatever value was actually assigned.']},
+ {'code': 'print(cast(list[int], raw) is raw)\nprint(type(raw).__name__)',
+ 'kind': 'cell',
+ 'line': 39,
+ 'output': 'True\ndict',
+ 'prose': ['`cast()` does not convert or validate the value. It returns the same '
+ 'object at runtime.']},
+ {'code': 'value: object = {"score": "98"}\n'
+ 'if isinstance(value, dict):\n'
+ ' print(value["score"])',
+ 'kind': 'cell',
+ 'line': 53,
+ 'output': '98',
+ 'prose': ['A real runtime check narrows by inspecting the value. This is safer when '
+ 'the input is untrusted.']}],
'code': 'from typing import Any, cast\n'
'\n'
'raw: Any = {"score": "98"}\n'
@@ -4067,37 +8893,72 @@
'print(score + 2)\n'
'print(cast(list[int], raw) is raw)\n'
'print(type(raw).__name__)\n',
+ 'doc_path': '/library/typing.html#typing.cast',
+ 'doc_url': 'https://docs.python.org/3.13/library/typing.html#typing.cast',
'expected_output': '100\nTrue\ndict\n',
+ 'explanation': ['`Any` and `cast()` are escape hatches. They are useful at messy boundaries '
+ 'where a type checker cannot prove what a value is, but they also remove '
+ 'protection when overused.',
+ '`Any` tells static tools to stop checking most operations on a value. `cast(T, '
+ 'value)` tells the type checker to treat a value as `T`, but it returns the same '
+ 'runtime object unchanged.',
+ 'Prefer narrowing with runtime checks when possible. Use `cast()` when another '
+ 'invariant already proves the type and the checker cannot see that proof.'],
+ 'min_python': None,
'notes': ['`Any` disables most static checking for a value.',
'`cast()` tells the type checker to trust you without changing the runtime object.',
'Prefer narrowing with checks when possible.'],
- 'cells': [{'prose': ['`Any` disables most static checking for a value. The runtime object is still whatever value '
- 'was actually assigned.'],
- 'code': 'from typing import Any, cast\n'
+ 'section': 'Types',
+ 'see_also': ['type-hints', 'runtime-type-checks', 'typed-dicts'],
+ 'slug': 'casts-and-any',
+ 'summary': 'Any and cast are escape hatches for places static analysis cannot prove.',
+ 'title': 'Casts and Any',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'from typing import Any, cast\n'
+ '\n'
+ 'raw: Any = {"score": "98"}\n'
+ 'score_text = cast(dict[str, str], raw)["score"]\n'
+ 'score = int(score_text)\n'
+ 'print(score + 2)',
+ 'prose': '`Any` disables most static checking for a value. The runtime object '
+ 'is still whatever value was actually assigned.'},
+ {'code': 'print(cast(list[int], raw) is raw)\nprint(type(raw).__name__)',
+ 'prose': '`cast()` does not convert or validate the value. It returns the same '
+ 'object at runtime.'},
+ {'code': 'value: object = {"score": "98"}\n'
+ 'if isinstance(value, dict):\n'
+ ' print(value["score"])',
+ 'prose': 'A real runtime check narrows by inspecting the value. This is safer '
+ 'when the input is untrusted.'}]},
+ {'cells': [{'code': 'from typing import NewType\n'
'\n'
- 'raw: Any = {"score": "98"}\n'
- 'score_text = cast(dict[str, str], raw)["score"]\n'
- 'score = int(score_text)\n'
- 'print(score + 2)',
- 'output': '100',
+ 'UserId = NewType("UserId", int)\n'
+ 'OrderId = NewType("OrderId", int)\n'
+ '\n'
+ '\n'
+ 'def load_user(user_id: UserId) -> str:\n'
+ ' return f"user {user_id}"\n'
+ '\n'
+ 'uid = UserId(42)\n'
+ 'print(load_user(uid))',
+ 'kind': 'cell',
'line': 22,
- 'kind': 'cell'},
- {'prose': ['`cast()` does not convert or validate the value. It returns the same object at runtime.'],
- 'code': 'print(cast(list[int], raw) is raw)\nprint(type(raw).__name__)',
- 'output': 'True\ndict',
- 'line': 39,
- 'kind': 'cell'},
- {'prose': ['A real runtime check narrows by inspecting the value. This is safer when the input is '
- 'untrusted.'],
- 'code': 'value: object = {"score": "98"}\nif isinstance(value, dict):\n print(value["score"])',
- 'output': '98',
- 'line': 53,
- 'kind': 'cell'}]},
- {'slug': 'newtype',
- 'title': 'NewType',
- 'section': 'Types',
- 'summary': 'NewType creates distinct static identities for runtime-compatible values.',
- 'doc_url': 'https://docs.python.org/3.13/library/typing.html#typing.NewType',
+ 'output': 'user 42',
+ 'prose': ['`NewType` helps type checkers distinguish values that share a runtime '
+ 'representation.']},
+ {'code': 'oid = OrderId(42)\nprint(uid == oid)\nprint(type(uid).__name__)',
+ 'kind': 'cell',
+ 'line': 44,
+ 'output': 'True\nint',
+ 'prose': ['At runtime, a `NewType` value is the underlying value. It compares like '
+ 'that value and has the same runtime type.']},
+ {'code': 'print(UserId.__name__)\nprint(OrderId.__name__)',
+ 'kind': 'cell',
+ 'line': 59,
+ 'output': 'UserId\nOrderId',
+ 'prose': ['The `NewType` constructor keeps a name for static tools and '
+ 'introspection.']}],
'code': 'from typing import NewType\n'
'\n'
'UserId = NewType("UserId", int)\n'
@@ -4113,41 +8974,84 @@
'print(uid == oid)\n'
'print(type(uid).__name__)\n'
'print(UserId.__name__)\n',
+ 'doc_path': '/library/typing.html#typing.NewType',
+ 'doc_url': 'https://docs.python.org/3.13/library/typing.html#typing.NewType',
'expected_output': 'user 42\nTrue\nint\nUserId\n',
+ 'explanation': ['`NewType` creates a distinct static identity for a value that is represented by '
+ 'an existing runtime type. It is useful for IDs, units, and other values that '
+ 'should not be mixed accidentally.',
+ 'The key boundary is static versus runtime behavior. A type checker can '
+ 'distinguish `UserId` from `OrderId`, but at runtime both values are plain '
+ 'integers.',
+ 'Use a type alias when you only want a clearer name for a shape. Use `NewType` '
+ 'when mixing two compatible shapes should be treated as a mistake by static '
+ 'analysis.'],
+ 'min_python': None,
'notes': ['`NewType` helps type checkers distinguish values that share a runtime representation.',
'At runtime, the value is still the underlying type.',
'Use aliases for readability; use `NewType` for static separation.'],
- 'cells': [{'prose': ['`NewType` helps type checkers distinguish values that share a runtime representation.'],
- 'code': 'from typing import NewType\n'
+ 'section': 'Types',
+ 'see_also': ['type-aliases', 'type-hints', 'runtime-type-checks'],
+ 'slug': 'newtype',
+ 'summary': 'NewType creates distinct static identities for runtime-compatible values.',
+ 'title': 'NewType',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'from typing import NewType\n'
+ '\n'
+ 'UserId = NewType("UserId", int)\n'
+ 'OrderId = NewType("OrderId", int)\n'
+ '\n'
+ '\n'
+ 'def load_user(user_id: UserId) -> str:\n'
+ ' return f"user {user_id}"\n'
+ '\n'
+ 'uid = UserId(42)\n'
+ 'print(load_user(uid))',
+ 'prose': '`NewType` helps type checkers distinguish values that share a runtime '
+ 'representation.'},
+ {'code': 'oid = OrderId(42)\nprint(uid == oid)\nprint(type(uid).__name__)',
+ 'prose': 'At runtime, a `NewType` value is the underlying value. It compares '
+ 'like that value and has the same runtime type.'},
+ {'code': 'print(UserId.__name__)\nprint(OrderId.__name__)',
+ 'prose': 'The `NewType` constructor keeps a name for static tools and '
+ 'introspection.'}]},
+ {'cells': [{'code': 'from typing import Protocol\n'
'\n'
- 'UserId = NewType("UserId", int)\n'
- 'OrderId = NewType("OrderId", int)\n'
+ 'class Greeter(Protocol):\n'
+ ' def greet(self) -> str:\n'
+ ' ...\n'
'\n'
+ 'print(Greeter.__name__)',
+ 'kind': 'cell',
+ 'line': 23,
+ 'output': 'Greeter',
+ 'prose': ['A protocol names required behavior. The ellipsis marks the method body as '
+ 'intentionally unspecified, similar to an interface declaration.']},
+ {'code': 'class Person:\n'
+ ' def __init__(self, name):\n'
+ ' self.name = name\n'
'\n'
- 'def load_user(user_id: UserId) -> str:\n'
- ' return f"user {user_id}"\n'
+ ' def greet(self):\n'
+ ' return f"hello {self.name}"\n'
'\n'
- 'uid = UserId(42)\n'
- 'print(load_user(uid))',
- 'output': 'user 42',
- 'line': 22,
- 'kind': 'cell'},
- {'prose': ['At runtime, a `NewType` value is the underlying value. It compares like that value and has the '
- 'same runtime type.'],
- 'code': 'oid = OrderId(42)\nprint(uid == oid)\nprint(type(uid).__name__)',
- 'output': 'True\nint',
- 'line': 44,
- 'kind': 'cell'},
- {'prose': ['The `NewType` constructor keeps a name for static tools and introspection.'],
- 'code': 'print(UserId.__name__)\nprint(OrderId.__name__)',
- 'output': 'UserId\nOrderId',
- 'line': 59,
- 'kind': 'cell'}]},
- {'slug': 'protocols',
- 'title': 'Protocols',
- 'section': 'Types',
- 'summary': 'Protocol describes required behavior for structural typing.',
- 'doc_url': 'https://docs.python.org/3.13/library/typing.html#typing.Protocol',
+ 'print(Person("Ada").greet())',
+ 'kind': 'cell',
+ 'line': 41,
+ 'output': 'hello Ada',
+ 'prose': ['A class can satisfy the protocol without inheriting from it. `Person` has '
+ 'a compatible `greet()` method, so it has the right shape for static type '
+ 'checkers.']},
+ {'code': 'def welcome(greeter: Greeter):\n'
+ ' print(greeter.greet())\n'
+ '\n'
+ 'welcome(Person("Ada"))',
+ 'kind': 'cell',
+ 'line': 60,
+ 'output': 'hello Ada',
+ 'prose': ['Use the protocol as an annotation at the API boundary. The function only '
+ 'cares that the object can greet; it does not care about the concrete '
+ 'class.']}],
'code': 'from typing import Protocol\n'
'\n'
'class Greeter(Protocol):\n'
@@ -4167,47 +9071,312 @@
'\n'
'welcome(Person("Ada"))\n'
'print(Greeter.__name__)\n',
+ 'doc_path': '/library/typing.html#typing.Protocol',
+ 'doc_url': 'https://docs.python.org/3.13/library/typing.html#typing.Protocol',
'expected_output': 'hello Ada\nGreeter\n',
- 'notes': ['Protocols are for structural typing: compatibility by shape rather than explicit inheritance.',
+ 'explanation': ['`Protocol` describes the methods or attributes an object must provide. It '
+ 'exists for structural typing: if an object has the right shape, type checkers '
+ 'can treat it as compatible.',
+ 'This is different from inheritance. Inheritance says a class is explicitly '
+ 'derived from a parent; a protocol says callers only need a particular behavior.',
+ 'At runtime, ordinary method lookup still applies. Protocols are mainly for '
+ 'static analysis, documentation, and API boundaries.'],
+ 'min_python': None,
+ 'notes': ['Protocols are for structural typing: compatibility by shape rather than explicit '
+ 'inheritance.',
'Type checkers understand protocols; normal runtime method calls still do the work.',
- 'Prefer inheritance when shared implementation matters, and protocols when only required behavior '
- 'matters.'],
- 'cells': [{'prose': ['A protocol names required behavior. The ellipsis marks the method body as intentionally '
- 'unspecified, similar to an interface declaration.'],
- 'code': 'from typing import Protocol\n'
- '\n'
- 'class Greeter(Protocol):\n'
- ' def greet(self) -> str:\n'
+ 'Prefer inheritance when shared implementation matters, and protocols when only '
+ 'required behavior matters.'],
+ 'section': 'Types',
+ 'see_also': ['type-hints', 'classes', 'inheritance-and-super', 'abstract-base-classes'],
+ 'slug': 'protocols',
+ 'summary': 'Protocol describes required behavior for structural typing.',
+ 'title': 'Protocols',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'from typing import Protocol\n'
+ '\n'
+ 'class Greeter(Protocol):\n'
+ ' def greet(self) -> str:\n'
+ ' ...\n'
+ '\n'
+ 'print(Greeter.__name__)',
+ 'prose': 'A protocol names required behavior. The ellipsis marks the method '
+ 'body as intentionally unspecified, similar to an interface '
+ 'declaration.'},
+ {'code': 'class Person:\n'
+ ' def __init__(self, name):\n'
+ ' self.name = name\n'
+ '\n'
+ ' def greet(self):\n'
+ ' return f"hello {self.name}"\n'
+ '\n'
+ 'print(Person("Ada").greet())',
+ 'prose': 'A class can satisfy the protocol without inheriting from it. `Person` '
+ 'has a compatible `greet()` method, so it has the right shape for '
+ 'static type checkers.'},
+ {'code': 'def welcome(greeter: Greeter):\n'
+ ' print(greeter.greet())\n'
+ '\n'
+ 'welcome(Person("Ada"))',
+ 'prose': 'Use the protocol as an annotation at the API boundary. The function '
+ 'only cares that the object can greet; it does not care about the '
+ 'concrete class.'}]},
+ {'cells': [{'code': 'from abc import ABC, abstractmethod\n'
+ '\n'
+ 'class Shape(ABC):\n'
+ ' @abstractmethod\n'
+ ' def area(self) -> float:\n'
' ...\n'
'\n'
- 'print(Greeter.__name__)',
- 'output': 'Greeter',
+ ' def describe(self) -> str:\n'
+ ' return f"shape with area {self.area()}"\n'
+ '\n'
+ 'try:\n'
+ ' Shape()\n'
+ 'except TypeError as error:\n'
+ ' print(error)',
+ 'kind': 'cell',
'line': 22,
- 'kind': 'cell'},
- {'prose': ['A class can satisfy the protocol without inheriting from it. `Person` has a compatible '
- '`greet()` method, so it has the right shape for static type checkers.'],
- 'code': 'class Person:\n'
- ' def __init__(self, name):\n'
- ' self.name = name\n'
+ 'output': "Can't instantiate abstract class Shape without an implementation for "
+ "abstract method 'area'",
+ 'prose': ['`ABC` plus `@abstractmethod` declares the contract. Trying to construct '
+ 'the base class itself fails because at least one method has no '
+ 'implementation. A concrete `describe()` lives alongside the abstract '
+ '`area()` so subclasses inherit shared behavior for free.']},
+ {'code': 'class Square(Shape):\n'
+ ' def __init__(self, side):\n'
+ ' self.side = side\n'
'\n'
- ' def greet(self):\n'
- ' return f"hello {self.name}"\n'
+ ' def area(self):\n'
+ ' return self.side ** 2\n'
'\n'
- 'print(Person("Ada").greet())',
- 'output': 'hello Ada',
- 'line': 40,
- 'kind': 'cell'},
- {'prose': ['Use the protocol as an annotation at the API boundary. The function only cares that the object '
- 'can greet; it does not care about the concrete class.'],
- 'code': 'def welcome(greeter: Greeter):\n print(greeter.greet())\n\nwelcome(Person("Ada"))',
- 'output': 'hello Ada',
- 'line': 59,
- 'kind': 'cell'}]},
- {'slug': 'enums',
- 'title': 'Enums',
- 'section': 'Types',
- 'summary': 'Enum defines symbolic names for a fixed set of values.',
- 'doc_url': 'https://docs.python.org/3.13/library/enum.html',
+ 'print(Square(3).area())\n'
+ 'print(Square(3).describe())',
+ 'kind': 'cell',
+ 'line': 47,
+ 'output': '9\nshape with area 9',
+ 'prose': ['A subclass that implements every abstract method is concrete and can be '
+ 'instantiated. It also inherits the non-abstract methods from the base '
+ 'class.']},
+ {'code': 'class Incomplete(Shape):\n'
+ ' pass\n'
+ '\n'
+ 'try:\n'
+ ' Incomplete()\n'
+ 'except TypeError as error:\n'
+ ' print(error)',
+ 'kind': 'cell',
+ 'line': 68,
+ 'output': "Can't instantiate abstract class Incomplete without an implementation for "
+ "abstract method 'area'",
+ 'prose': ['A subclass that forgets to implement an abstract method also cannot be '
+ 'instantiated — that is the value the ABC adds. The error fires at '
+ 'construction, not when something later tries to call the missing method.']},
+ {'code': 'from typing import Protocol\n'
+ '\n'
+ 'class HasArea(Protocol):\n'
+ ' def area(self) -> float:\n'
+ ' ...\n'
+ '\n'
+ 'class Triangle:\n'
+ ' def __init__(self, base, height):\n'
+ ' self.base = base\n'
+ ' self.height = height\n'
+ '\n'
+ ' def area(self):\n'
+ ' return 0.5 * self.base * self.height\n'
+ '\n'
+ 'def total_area(shapes: list[HasArea]) -> float:\n'
+ ' return sum(shape.area() for shape in shapes)\n'
+ '\n'
+ 'print(total_area([Square(3), Triangle(4, 3)]))\n'
+ 'print(isinstance(Triangle(4, 3), Shape))\n'
+ 'print(isinstance(Square(3), Shape))',
+ 'kind': 'cell',
+ 'line': 86,
+ 'output': '15.0\nFalse\nTrue',
+ 'prose': ['Contrast with `Protocol`. A `HasArea` protocol accepts any class with an '
+ '`area()` method, no inheritance required. `Triangle` does not inherit from '
+ '`Shape`, so it satisfies the protocol but fails `isinstance(_, Shape)`. '
+ '`Square` satisfies both because it explicitly inherited from the ABC.']}],
+ 'code': 'from abc import ABC, abstractmethod\n'
+ 'from typing import Protocol\n'
+ '\n'
+ 'class Shape(ABC):\n'
+ ' @abstractmethod\n'
+ ' def area(self) -> float:\n'
+ ' ...\n'
+ '\n'
+ ' def describe(self) -> str:\n'
+ ' return f"shape with area {self.area()}"\n'
+ '\n'
+ 'try:\n'
+ ' Shape()\n'
+ 'except TypeError as error:\n'
+ ' print(error)\n'
+ '\n'
+ 'class Square(Shape):\n'
+ ' def __init__(self, side):\n'
+ ' self.side = side\n'
+ '\n'
+ ' def area(self):\n'
+ ' return self.side ** 2\n'
+ '\n'
+ 'print(Square(3).area())\n'
+ 'print(Square(3).describe())\n'
+ '\n'
+ 'class Incomplete(Shape):\n'
+ ' pass\n'
+ '\n'
+ 'try:\n'
+ ' Incomplete()\n'
+ 'except TypeError as error:\n'
+ ' print(error)\n'
+ '\n'
+ 'class HasArea(Protocol):\n'
+ ' def area(self) -> float:\n'
+ ' ...\n'
+ '\n'
+ 'class Triangle:\n'
+ ' def __init__(self, base, height):\n'
+ ' self.base = base\n'
+ ' self.height = height\n'
+ '\n'
+ ' def area(self):\n'
+ ' return 0.5 * self.base * self.height\n'
+ '\n'
+ 'def total_area(shapes: list[HasArea]) -> float:\n'
+ ' return sum(shape.area() for shape in shapes)\n'
+ '\n'
+ 'print(total_area([Square(3), Triangle(4, 3)]))\n'
+ 'print(isinstance(Triangle(4, 3), Shape))\n'
+ 'print(isinstance(Square(3), Shape))\n',
+ 'doc_path': '/library/abc.html',
+ 'doc_url': 'https://docs.python.org/3.13/library/abc.html',
+ 'expected_output': "Can't instantiate abstract class Shape without an implementation for "
+ "abstract method 'area'\n"
+ '9\n'
+ 'shape with area 9\n'
+ "Can't instantiate abstract class Incomplete without an implementation for "
+ "abstract method 'area'\n"
+ '15.0\n'
+ 'False\n'
+ 'True\n',
+ 'explanation': ['`ABC` and `@abstractmethod` describe an interface that subclasses must '
+ 'implement. The base class refuses to instantiate until a concrete subclass '
+ 'provides every abstract method, which catches "I forgot to implement this" at '
+ 'construction time rather than at the first method call.',
+ 'ABCs are different from `Protocol`. An ABC is nominal: a class participates in '
+ 'the contract by inheriting from it. A `Protocol` is structural: any class with '
+ 'the right methods qualifies, no inheritance required. Reach for an ABC when you '
+ 'want shared implementation in the base class or you want `isinstance()` to mean '
+ '"explicitly opted in"; reach for a `Protocol` when you only care about behavior '
+ 'at the API boundary.',
+ 'The cost is a small amount of ceremony at the type level. The benefit is that a '
+ 'half-implemented subclass cannot be created by accident.'],
+ 'min_python': None,
+ 'notes': ['`ABC` plus `@abstractmethod` blocks instantiation until every abstract method has an '
+ 'implementation.',
+ 'ABCs are nominal — subclasses opt in by inheriting; `isinstance()` reflects that '
+ 'opt-in.',
+ 'Protocols are structural — any class with the right shape qualifies, regardless of '
+ 'inheritance.',
+ 'Prefer an ABC when shared implementation or explicit opt-in matters; prefer a '
+ 'Protocol when only behavior at the API boundary matters.'],
+ 'section': 'Classes',
+ 'see_also': ['protocols', 'inheritance-and-super', 'classes'],
+ 'slug': 'abstract-base-classes',
+ 'summary': 'ABC and abstractmethod enforce that subclasses implement required methods.',
+ 'title': 'Abstract Base Classes',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'from abc import ABC, abstractmethod\n'
+ '\n'
+ 'class Shape(ABC):\n'
+ ' @abstractmethod\n'
+ ' def area(self) -> float:\n'
+ ' ...\n'
+ '\n'
+ ' def describe(self) -> str:\n'
+ ' return f"shape with area {self.area()}"\n'
+ '\n'
+ 'try:\n'
+ ' Shape()\n'
+ 'except TypeError as error:\n'
+ ' print(error)',
+ 'prose': '`ABC` plus `@abstractmethod` declares the contract. Trying to '
+ 'construct the base class itself fails because at least one method has '
+ 'no implementation. A concrete `describe()` lives alongside the '
+ 'abstract `area()` so subclasses inherit shared behavior for free.'},
+ {'code': 'class Square(Shape):\n'
+ ' def __init__(self, side):\n'
+ ' self.side = side\n'
+ '\n'
+ ' def area(self):\n'
+ ' return self.side ** 2\n'
+ '\n'
+ 'print(Square(3).area())\n'
+ 'print(Square(3).describe())',
+ 'prose': 'A subclass that implements every abstract method is concrete and can '
+ 'be instantiated. It also inherits the non-abstract methods from the '
+ 'base class.'},
+ {'code': 'class Incomplete(Shape):\n'
+ ' pass\n'
+ '\n'
+ 'try:\n'
+ ' Incomplete()\n'
+ 'except TypeError as error:\n'
+ ' print(error)',
+ 'prose': 'A subclass that forgets to implement an abstract method also cannot '
+ 'be instantiated — that is the value the ABC adds. The error fires at '
+ 'construction, not when something later tries to call the missing '
+ 'method.'},
+ {'code': 'from typing import Protocol\n'
+ '\n'
+ 'class HasArea(Protocol):\n'
+ ' def area(self) -> float:\n'
+ ' ...\n'
+ '\n'
+ 'class Triangle:\n'
+ ' def __init__(self, base, height):\n'
+ ' self.base = base\n'
+ ' self.height = height\n'
+ '\n'
+ ' def area(self):\n'
+ ' return 0.5 * self.base * self.height\n'
+ '\n'
+ 'def total_area(shapes: list[HasArea]) -> float:\n'
+ ' return sum(shape.area() for shape in shapes)\n'
+ '\n'
+ 'print(total_area([Square(3), Triangle(4, 3)]))\n'
+ 'print(isinstance(Triangle(4, 3), Shape))\n'
+ 'print(isinstance(Square(3), Shape))',
+ 'prose': 'Contrast with `Protocol`. A `HasArea` protocol accepts any class with '
+ 'an `area()` method, no inheritance required. `Triangle` does not '
+ 'inherit from `Shape`, so it satisfies the protocol but fails '
+ '`isinstance(_, Shape)`. `Square` satisfies both because it explicitly '
+ 'inherited from the ABC.'}]},
+ {'cells': [{'code': 'from enum import Enum\n'
+ '\n'
+ 'class Status(Enum):\n'
+ ' PENDING = "pending"\n'
+ ' DONE = "done"\n'
+ '\n'
+ 'current = Status.PENDING\n'
+ 'print(current.name)\n'
+ 'print(current.value)',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': 'PENDING\npending',
+ 'prose': ['An enum member has a symbolic name and an underlying value. The symbolic '
+ 'name is what readers usually care about in code.']},
+ {'code': 'print(current is Status.PENDING)\nprint(current == "pending")',
+ 'kind': 'cell',
+ 'line': 38,
+ 'output': 'True\nFalse',
+ 'prose': ['Compare enum members with enum members, not with raw strings. This keeps '
+ 'the set of valid states explicit.']}],
'code': 'from enum import Enum\n'
'\n'
'class Status(Enum):\n'
@@ -4219,36 +9388,91 @@
'print(current.value)\n'
'print(current is Status.PENDING)\n'
'print(current == "pending")\n',
+ 'doc_path': '/library/enum.html',
+ 'doc_url': 'https://docs.python.org/3.13/library/enum.html',
'expected_output': 'PENDING\npending\nTrue\nFalse\n',
+ 'explanation': ['`Enum` defines a fixed set of named values. This makes states and modes easier '
+ 'to read than raw strings scattered through a program.',
+ 'Each enum member has a name and a value. Comparing enum members is explicit and '
+ 'helps avoid typos that plain strings would allow.',
+ 'Use enums when a value must be one of a small known set: statuses, modes, '
+ 'directions, roles, and similar choices.'],
+ 'min_python': None,
'notes': ['Enums make states and choices explicit.',
'Members have names and values.',
'Comparing enum members avoids string typo bugs.',
- 'Prefer raw strings for open-ended text; prefer enums for a closed set of named choices.'],
- 'cells': [{'prose': ['An enum member has a symbolic name and an underlying value. The symbolic name is what readers '
- 'usually care about in code.'],
- 'code': 'from enum import Enum\n'
+ 'Prefer raw strings for open-ended text; prefer enums for a closed set of named '
+ 'choices.'],
+ 'section': 'Types',
+ 'see_also': [],
+ 'slug': 'enums',
+ 'summary': 'Enum defines symbolic names for a fixed set of values.',
+ 'title': 'Enums',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'from enum import Enum\n'
+ '\n'
+ 'class Status(Enum):\n'
+ ' PENDING = "pending"\n'
+ ' DONE = "done"\n'
+ '\n'
+ 'current = Status.PENDING\n'
+ 'print(current.name)\n'
+ 'print(current.value)',
+ 'prose': 'An enum member has a symbolic name and an underlying value. The '
+ 'symbolic name is what readers usually care about in code.'},
+ {'code': 'print(current is Status.PENDING)\nprint(current == "pending")',
+ 'prose': 'Compare enum members with enum members, not with raw strings. This '
+ 'keeps the set of valid states explicit.'}]},
+ {'cells': [{'code': 'import re\n'
'\n'
- 'class Status(Enum):\n'
- ' PENDING = "pending"\n'
- ' DONE = "done"\n'
+ 'text = "Ada: 10, Grace: 9"\n'
+ 'pattern = r"([A-Za-z]+): (\\d+)"\n'
'\n'
- 'current = Status.PENDING\n'
- 'print(current.name)\n'
- 'print(current.value)',
- 'output': 'PENDING\npending',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['Compare enum members with enum members, not with raw strings. This keeps the set of valid '
- 'states explicit.'],
- 'code': 'print(current is Status.PENDING)\nprint(current == "pending")',
- 'output': 'True\nFalse',
- 'line': 38,
- 'kind': 'cell'}]},
- {'slug': 'regular-expressions',
- 'title': 'Regular Expressions',
- 'section': 'Text',
- 'summary': 'The re module searches and extracts text using regular expressions.',
- 'doc_url': 'https://docs.python.org/3.13/library/re.html',
+ 'for name, score in re.findall(pattern, text):\n'
+ ' print(name, int(score))',
+ 'kind': 'cell',
+ 'line': 21,
+ 'output': 'Ada 10\nGrace 9',
+ 'prose': ['Raw strings keep backslashes readable in regex patterns. Capturing groups '
+ 'return just the pieces inside parentheses.']},
+ {'code': 'match = re.search(r"Grace: (\\d+)", text)\nprint(match.group(1))',
+ 'kind': 'cell',
+ 'line': 40,
+ 'output': '9',
+ 'prose': ['`re.search()` finds the first match. A match object exposes captured '
+ 'groups by position.']},
+ {'code': 'print("Grace" in text)',
+ 'kind': 'cell',
+ 'line': 53,
+ 'output': 'True',
+ 'prose': ['For a simple substring check, ordinary string membership is clearer than '
+ 'regex.']},
+ {'code': 'start = re.match(r"Ada", text)\n'
+ 'print(start is not None)\n'
+ 'print(re.match(r"Grace", text))',
+ 'kind': 'cell',
+ 'line': 65,
+ 'output': 'True\nNone',
+ 'prose': ['`re.match` only matches at the start of the string; `re.search` finds the '
+ 'first match anywhere. Picking the right one keeps anchoring intent visible '
+ 'without an explicit `^`.']},
+ {'code': 'scoreline = re.compile(pattern)\nprint(scoreline.findall(text))',
+ 'kind': 'cell',
+ 'line': 80,
+ 'output': "[('Ada', '10'), ('Grace', '9')]",
+ 'prose': ['`re.compile` produces a reusable pattern object whose methods skip the '
+ 'parser on each call. Reach for it when the same pattern runs in a loop.']},
+ {'code': 'casey = "ADA: 11"\n'
+ 'print(re.search(r"ada", casey, flags=re.IGNORECASE).group(0))\n'
+ '\n'
+ 'print(re.sub(r"\\d+", "?", text))',
+ 'kind': 'cell',
+ 'line': 93,
+ 'output': 'ADA\nAda: ?, Grace: ?',
+ 'prose': ['Flags such as `re.IGNORECASE` adjust matching without changing the '
+ 'pattern. `re.sub` replaces every match with a replacement string and '
+ 'returns the rewritten text.']}],
'code': 'import re\n'
'\n'
'text = "Ada: 10, Grace: 9"\n'
@@ -4259,38 +9483,102 @@
'\n'
'match = re.search(r"Grace: (\\d+)", text)\n'
'print(match.group(1))\n'
- 'print("Grace" in text)\n',
- 'expected_output': 'Ada 10\nGrace 9\n9\nTrue\n',
+ 'print("Grace" in text)\n'
+ '\n'
+ 'start = re.match(r"Ada", text)\n'
+ 'print(start is not None)\n'
+ 'print(re.match(r"Grace", text))\n'
+ '\n'
+ 'scoreline = re.compile(pattern)\n'
+ 'print(scoreline.findall(text))\n'
+ '\n'
+ 'casey = "ADA: 11"\n'
+ 'print(re.search(r"ada", casey, flags=re.IGNORECASE).group(0))\n'
+ '\n'
+ 'print(re.sub(r"\\d+", "?", text))\n',
+ 'doc_path': '/library/re.html',
+ 'doc_url': 'https://docs.python.org/3.13/library/re.html',
+ 'expected_output': 'Ada 10\n'
+ 'Grace 9\n'
+ '9\n'
+ 'True\n'
+ 'True\n'
+ 'None\n'
+ "[('Ada', '10'), ('Grace', '9')]\n"
+ 'ADA\n'
+ 'Ada: ?, Grace: ?\n',
+ 'explanation': ['Regular expressions are a compact language for searching and extracting text '
+ "patterns. Python's `re` module provides the standard interface: `re.match` "
+ 'anchors at the start of the string, `re.search` finds the first occurrence '
+ 'anywhere, `re.findall` collects every match, `re.sub` rewrites matches, and '
+ '`re.compile` reuses a pattern.',
+ 'Use regex when the pattern has structure: repeated records, alternatives, '
+ 'optional parts, or pieces you want to capture. Prefer ordinary string methods '
+ 'for simple substring checks because simpler code is easier to maintain.',
+ 'Flags such as `re.IGNORECASE` adjust matching behavior without rewriting the '
+ 'pattern. Pair them with `re.compile` when the same pattern is used repeatedly.'],
+ 'min_python': None,
'notes': ['Use raw strings for regex patterns so backslashes are easier to read.',
'Use capturing groups when the point is extraction, not just matching.',
+ '`re.match` anchors at the start; `re.search` finds the first match anywhere.',
+ '`re.compile` saves work when the pattern runs more than once.',
+ '`re.sub` rewrites matches; flags like `re.IGNORECASE` change matching behavior '
+ 'without rewriting the pattern.',
'Reach for string methods before regex when the pattern is simple.'],
- 'cells': [{'prose': ['Raw strings keep backslashes readable in regex patterns. Capturing groups return just the '
- 'pieces inside parentheses.'],
- 'code': 'import re\n'
- '\n'
- 'text = "Ada: 10, Grace: 9"\n'
- 'pattern = r"([A-Za-z]+): (\\d+)"\n'
- '\n'
- 'for name, score in re.findall(pattern, text):\n'
- ' print(name, int(score))',
- 'output': 'Ada 10\nGrace 9',
+ 'section': 'Text',
+ 'see_also': ['strings', 'string-formatting'],
+ 'slug': 'regular-expressions',
+ 'summary': 'The re module searches and extracts text using regular expressions.',
+ 'title': 'Regular Expressions',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'import re\n'
+ '\n'
+ 'text = "Ada: 10, Grace: 9"\n'
+ 'pattern = r"([A-Za-z]+): (\\d+)"\n'
+ '\n'
+ 'for name, score in re.findall(pattern, text):\n'
+ ' print(name, int(score))',
+ 'prose': 'Raw strings keep backslashes readable in regex patterns. Capturing '
+ 'groups return just the pieces inside parentheses.'},
+ {'code': 'match = re.search(r"Grace: (\\d+)", text)\nprint(match.group(1))',
+ 'prose': '`re.search()` finds the first match. A match object exposes captured '
+ 'groups by position.'},
+ {'code': 'print("Grace" in text)',
+ 'prose': 'For a simple substring check, ordinary string membership is clearer '
+ 'than regex.'},
+ {'code': 'start = re.match(r"Ada", text)\n'
+ 'print(start is not None)\n'
+ 'print(re.match(r"Grace", text))',
+ 'prose': '`re.match` only matches at the start of the string; `re.search` finds '
+ 'the first match anywhere. Picking the right one keeps anchoring '
+ 'intent visible without an explicit `^`.'},
+ {'code': 'scoreline = re.compile(pattern)\nprint(scoreline.findall(text))',
+ 'prose': '`re.compile` produces a reusable pattern object whose methods skip '
+ 'the parser on each call. Reach for it when the same pattern runs in a '
+ 'loop.'},
+ {'code': 'casey = "ADA: 11"\n'
+ 'print(re.search(r"ada", casey, flags=re.IGNORECASE).group(0))\n'
+ '\n'
+ 'print(re.sub(r"\\d+", "?", text))',
+ 'prose': 'Flags such as `re.IGNORECASE` adjust matching without changing the '
+ 'pattern. `re.sub` replaces every match with a replacement string and '
+ 'returns the rewritten text.'}]},
+ {'cells': [{'code': 'print(int("42"))\nprint(float("3.5"))',
+ 'kind': 'cell',
'line': 17,
- 'kind': 'cell'},
- {'prose': ['`re.search()` finds the first match. A match object exposes captured groups by position.'],
- 'code': 'match = re.search(r"Grace: (\\d+)", text)\nprint(match.group(1))',
- 'output': '9',
- 'line': 36,
- 'kind': 'cell'},
- {'prose': ['For a simple substring check, ordinary string membership is clearer than regex.'],
- 'code': 'print("Grace" in text)',
- 'output': 'True',
- 'line': 49,
- 'kind': 'cell'}]},
- {'slug': 'number-parsing',
- 'title': 'Number Parsing',
- 'section': 'Standard Library',
- 'summary': 'int() and float() parse text into numbers and raise ValueError on bad input.',
- 'doc_url': 'https://docs.python.org/3.13/library/functions.html#int',
+ 'output': '42\n3.5',
+ 'prose': ['Use `int()` for whole numbers and `float()` for decimal text. Parsed '
+ 'values are real numbers, not strings.']},
+ {'code': 'try:\n'
+ ' int("python")\n'
+ 'except ValueError as error:\n'
+ ' print(type(error).__name__)',
+ 'kind': 'cell',
+ 'line': 31,
+ 'output': 'ValueError',
+ 'prose': ['Bad numeric text raises `ValueError`. Catch that specific exception when '
+ 'invalid input is part of the normal flow.']}],
'code': 'print(int("42"))\n'
'print(float("3.5"))\n'
'\n'
@@ -4298,26 +9586,57 @@
' int("python")\n'
'except ValueError as error:\n'
' print(type(error).__name__)\n',
+ 'doc_path': '/library/functions.html#int',
+ 'doc_url': 'https://docs.python.org/3.13/library/functions.html#int',
'expected_output': '42\n3.5\nValueError\n',
+ 'explanation': ['Parsing turns text into numeric values. `int()` parses whole-number text and '
+ '`float()` parses decimal or scientific-notation text.',
+ 'Invalid numeric text raises `ValueError`. Catching that specific exception '
+ 'makes it clear that bad input is expected and recoverable.',
+ 'After parsing, the result is a number and can participate in arithmetic; before '
+ 'parsing, it is just text.'],
+ 'min_python': None,
'notes': ['`int()` and `float()` are constructors that also parse strings.',
'Catch `ValueError` when bad user input is expected and recoverable.'],
- 'cells': [{'prose': ['Use `int()` for whole numbers and `float()` for decimal text. Parsed values are real numbers, '
- 'not strings.'],
- 'code': 'print(int("42"))\nprint(float("3.5"))',
- 'output': '42\n3.5',
+ 'section': 'Standard Library',
+ 'see_also': [],
+ 'slug': 'number-parsing',
+ 'summary': 'int() and float() parse text into numbers and raise ValueError on bad input.',
+ 'title': 'Number Parsing',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'print(int("42"))\nprint(float("3.5"))',
+ 'prose': 'Use `int()` for whole numbers and `float()` for decimal text. Parsed '
+ 'values are real numbers, not strings.'},
+ {'code': 'try:\n'
+ ' int("python")\n'
+ 'except ValueError as error:\n'
+ ' print(type(error).__name__)',
+ 'prose': 'Bad numeric text raises `ValueError`. Catch that specific exception '
+ 'when invalid input is part of the normal flow.'}]},
+ {'cells': [{'code': 'class EmptyCartError(Exception):\n pass\n\nprint(EmptyCartError.__name__)',
+ 'kind': 'cell',
'line': 17,
- 'kind': 'cell'},
- {'prose': ['Bad numeric text raises `ValueError`. Catch that specific exception when invalid input is part '
- 'of the normal flow.'],
- 'code': 'try:\n int("python")\nexcept ValueError as error:\n print(type(error).__name__)',
- 'output': 'ValueError',
- 'line': 31,
- 'kind': 'cell'}]},
- {'slug': 'custom-exceptions',
- 'title': 'Custom Exceptions',
- 'section': 'Errors',
- 'summary': 'Custom exception classes name failures that belong to your domain.',
- 'doc_url': 'https://docs.python.org/3.13/tutorial/errors.html#user-defined-exceptions',
+ 'output': 'EmptyCartError',
+ 'prose': ['Create a custom exception when a failure has a name in your problem '
+ 'domain. The class can be empty at first.']},
+ {'code': 'def checkout(items):\n'
+ ' if not items:\n'
+ ' raise EmptyCartError("cart is empty")\n'
+ ' return "paid"\n'
+ '\n'
+ 'print(checkout(["book"]))',
+ 'kind': 'cell',
+ 'line': 32,
+ 'output': 'paid',
+ 'prose': ['Raise the custom exception where the invalid state is detected. Normal '
+ 'inputs still follow the ordinary success path.']},
+ {'code': 'try:\n checkout([])\nexcept EmptyCartError as error:\n print(error)',
+ 'kind': 'cell',
+ 'line': 49,
+ 'output': 'cart is empty',
+ 'prose': ['Callers can catch the precise error type without accidentally catching '
+ 'unrelated failures.']}],
'code': 'class EmptyCartError(Exception):\n'
' pass\n'
'\n'
@@ -4335,37 +9654,86 @@
' checkout([])\n'
'except EmptyCartError as error:\n'
' print(error)\n',
+ 'doc_path': '/tutorial/errors.html#user-defined-exceptions',
+ 'doc_url': 'https://docs.python.org/3.13/tutorial/errors.html#user-defined-exceptions',
'expected_output': 'EmptyCartError\npaid\ncart is empty\n',
+ 'explanation': ['Custom exceptions give names to failures in your problem domain. A named '
+ 'exception is easier to catch and explain than a generic error with only a '
+ 'string message.',
+ 'Raise the custom exception at the point where the invalid state is discovered. '
+ 'Include a message for the specific occurrence.',
+ 'Catch custom exceptions at the boundary where recovery makes sense, such as '
+ 'returning an error response or asking for corrected input.'],
+ 'min_python': None,
'notes': ['Subclass `Exception` for errors callers are expected to catch.',
- 'A custom exception name can be clearer than reusing a generic `ValueError` everywhere.',
+ 'A custom exception name can be clearer than reusing a generic `ValueError` '
+ 'everywhere.',
'Catch custom exceptions at a boundary that can recover or report clearly.'],
- 'cells': [{'prose': ['Create a custom exception when a failure has a name in your problem domain. The class can be '
- 'empty at first.'],
- 'code': 'class EmptyCartError(Exception):\n pass\n\nprint(EmptyCartError.__name__)',
- 'output': 'EmptyCartError',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['Raise the custom exception where the invalid state is detected. Normal inputs still follow the '
- 'ordinary success path.'],
- 'code': 'def checkout(items):\n'
- ' if not items:\n'
- ' raise EmptyCartError("cart is empty")\n'
- ' return "paid"\n'
- '\n'
- 'print(checkout(["book"]))',
- 'output': 'paid',
- 'line': 32,
- 'kind': 'cell'},
- {'prose': ['Callers can catch the precise error type without accidentally catching unrelated failures.'],
- 'code': 'try:\n checkout([])\nexcept EmptyCartError as error:\n print(error)',
- 'output': 'cart is empty',
- 'line': 49,
- 'kind': 'cell'}]},
- {'slug': 'json',
- 'title': 'JSON',
- 'section': 'Standard Library',
- 'summary': 'json encodes Python values as JSON text and decodes them back.',
- 'doc_url': 'https://docs.python.org/3.13/library/json.html',
+ 'section': 'Errors',
+ 'see_also': [],
+ 'slug': 'custom-exceptions',
+ 'summary': 'Custom exception classes name failures that belong to your domain.',
+ 'title': 'Custom Exceptions',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'class EmptyCartError(Exception):\n'
+ ' pass\n'
+ '\n'
+ 'print(EmptyCartError.__name__)',
+ 'prose': 'Create a custom exception when a failure has a name in your problem '
+ 'domain. The class can be empty at first.'},
+ {'code': 'def checkout(items):\n'
+ ' if not items:\n'
+ ' raise EmptyCartError("cart is empty")\n'
+ ' return "paid"\n'
+ '\n'
+ 'print(checkout(["book"]))',
+ 'prose': 'Raise the custom exception where the invalid state is detected. '
+ 'Normal inputs still follow the ordinary success path.'},
+ {'code': 'try:\n'
+ ' checkout([])\n'
+ 'except EmptyCartError as error:\n'
+ ' print(error)',
+ 'prose': 'Callers can catch the precise error type without accidentally '
+ 'catching unrelated failures.'}]},
+ {'cells': [{'code': 'import json\n'
+ '\n'
+ 'payload = {"language": "Python", "versions": [3, 13], "stable": True, '
+ '"missing": None}\n'
+ 'text = json.dumps(payload, sort_keys=True)\n'
+ 'print(text)',
+ 'kind': 'cell',
+ 'line': 22,
+ 'output': '{"language": "Python", "missing": null, "stable": true, "versions": [3, '
+ '13]}',
+ 'prose': ['`dumps()` encodes Python data as JSON text. `sort_keys=True` keeps '
+ 'dictionary keys in a stable order for reproducible output.']},
+ {'code': 'pretty = json.dumps({"language": "Python", "stable": True}, indent=2, '
+ 'sort_keys=True)\n'
+ 'print(pretty.splitlines()[0])\n'
+ 'print(pretty.splitlines()[1])',
+ 'kind': 'cell',
+ 'line': 38,
+ 'output': '{\n "language": "Python",',
+ 'prose': ['Formatting options change the JSON text, not the Python value. `indent=2` '
+ 'is useful for human-readable output.']},
+ {'code': 'decoded = json.loads(text)\n'
+ 'print(decoded["language"])\n'
+ 'print(decoded["missing"] is None)',
+ 'kind': 'cell',
+ 'line': 53,
+ 'output': 'Python\nTrue',
+ 'prose': ['`loads()` decodes JSON text back into Python values. JSON `null` becomes '
+ 'Python `None`.']},
+ {'code': 'try:\n'
+ ' json.loads("{bad json}")\n'
+ 'except json.JSONDecodeError as error:\n'
+ ' print(error.__class__.__name__)',
+ 'kind': 'cell',
+ 'line': 68,
+ 'output': 'JSONDecodeError',
+ 'prose': ['Invalid JSON raises `JSONDecodeError`, so input boundaries should handle '
+ 'decode failures explicitly.']}],
'code': 'import json\n'
'\n'
'payload = {"language": "Python", "versions": [3, 13], "stable": True, "missing": None}\n'
@@ -4384,53 +9752,76 @@
' json.loads("{bad json}")\n'
'except json.JSONDecodeError as error:\n'
' print(error.__class__.__name__)\n',
- 'expected_output': '{"language": "Python", "missing": null, "stable": true, "versions": [3, 13]}\n'
+ 'doc_path': '/library/json.html',
+ 'doc_url': 'https://docs.python.org/3.13/library/json.html',
+ 'expected_output': '{"language": "Python", "missing": null, "stable": true, "versions": [3, '
+ '13]}\n'
'{\n'
' "language": "Python",\n'
'Python\n'
'True\n'
'JSONDecodeError\n',
+ 'explanation': ['The `json` module converts between Python values and JSON text. Dictionaries, '
+ 'lists, strings, numbers, booleans, and `None` map naturally to JSON structures.',
+ 'Use `dumps()` when you need a string and `loads()` when you need Python objects '
+ 'back. Options such as `sort_keys=True` and `indent=2` control stable, readable '
+ 'output.',
+ 'JSON is a data format, not a way to preserve arbitrary Python objects. Encode '
+ 'simple data structures at service boundaries, and expect decode errors when the '
+ 'incoming text is not valid JSON.'],
+ 'min_python': None,
'notes': ['`dumps()` returns a string; `loads()` accepts a string.',
'JSON `true`, `false`, and `null` become Python `True`, `False`, and `None`.',
'Use `sort_keys=True` when stable text output matters.',
'JSON only represents data shapes, not arbitrary Python objects or behavior.'],
- 'cells': [{'prose': ['`dumps()` encodes Python data as JSON text. `sort_keys=True` keeps dictionary keys in a stable '
- 'order for reproducible output.'],
- 'code': 'import json\n'
- '\n'
- 'payload = {"language": "Python", "versions": [3, 13], "stable": True, "missing": None}\n'
- 'text = json.dumps(payload, sort_keys=True)\n'
- 'print(text)',
- 'output': '{"language": "Python", "missing": null, "stable": true, "versions": [3, 13]}',
- 'line': 22,
- 'kind': 'cell'},
- {'prose': ['Formatting options change the JSON text, not the Python value. `indent=2` is useful for '
- 'human-readable output.'],
- 'code': 'pretty = json.dumps({"language": "Python", "stable": True}, indent=2, sort_keys=True)\n'
- 'print(pretty.splitlines()[0])\n'
- 'print(pretty.splitlines()[1])',
- 'output': '{\n "language": "Python",',
- 'line': 38,
- 'kind': 'cell'},
- {'prose': ['`loads()` decodes JSON text back into Python values. JSON `null` becomes Python `None`.'],
- 'code': 'decoded = json.loads(text)\nprint(decoded["language"])\nprint(decoded["missing"] is None)',
- 'output': 'Python\nTrue',
- 'line': 53,
- 'kind': 'cell'},
- {'prose': ['Invalid JSON raises `JSONDecodeError`, so input boundaries should handle decode failures '
- 'explicitly.'],
- 'code': 'try:\n'
- ' json.loads("{bad json}")\n'
- 'except json.JSONDecodeError as error:\n'
- ' print(error.__class__.__name__)',
- 'output': 'JSONDecodeError',
- 'line': 68,
- 'kind': 'cell'}]},
- {'slug': 'logging',
- 'title': 'Logging',
'section': 'Standard Library',
- 'summary': 'logging records operational events without using print as infrastructure.',
- 'doc_url': 'https://docs.python.org/3.13/library/logging.html',
+ 'see_also': ['dicts', 'typed-dicts', 'strings'],
+ 'slug': 'json',
+ 'summary': 'json encodes Python values as JSON text and decodes them back.',
+ 'title': 'JSON',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'import json\n'
+ '\n'
+ 'payload = {"language": "Python", "versions": [3, 13], "stable": True, '
+ '"missing": None}\n'
+ 'text = json.dumps(payload, sort_keys=True)\n'
+ 'print(text)',
+ 'prose': '`dumps()` encodes Python data as JSON text. `sort_keys=True` keeps '
+ 'dictionary keys in a stable order for reproducible output.'},
+ {'code': 'pretty = json.dumps({"language": "Python", "stable": True}, indent=2, '
+ 'sort_keys=True)\n'
+ 'print(pretty.splitlines()[0])\n'
+ 'print(pretty.splitlines()[1])',
+ 'prose': 'Formatting options change the JSON text, not the Python value. '
+ '`indent=2` is useful for human-readable output.'},
+ {'code': 'decoded = json.loads(text)\n'
+ 'print(decoded["language"])\n'
+ 'print(decoded["missing"] is None)',
+ 'prose': '`loads()` decodes JSON text back into Python values. JSON `null` '
+ 'becomes Python `None`.'},
+ {'code': 'try:\n'
+ ' json.loads("{bad json}")\n'
+ 'except json.JSONDecodeError as error:\n'
+ ' print(error.__class__.__name__)',
+ 'prose': 'Invalid JSON raises `JSONDecodeError`, so input boundaries should '
+ 'handle decode failures explicitly.'}]},
+ {'cells': [{'code': 'import logging\n'
+ 'import sys\n'
+ '\n'
+ 'logger = logging.getLogger("example")\n'
+ 'logger.setLevel(logging.INFO)\n'
+ 'handler = logging.StreamHandler(sys.stdout)\n'
+ 'handler.setFormatter(logging.Formatter("%(levelname)s:%(message)s"))\n'
+ 'logger.handlers[:] = [handler]\n'
+ '\n'
+ 'logger.debug("hidden")\n'
+ 'logger.info("service started")\n'
+ 'logger.warning("disk almost full")',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': 'INFO:service started\nWARNING:disk almost full',
+ 'prose': ['Loggers name where an event came from.']}],
'code': 'import logging\n'
'import sys\n'
'\n'
@@ -4443,31 +9834,93 @@
'logger.debug("hidden")\n'
'logger.info("service started")\n'
'logger.warning("disk almost full")\n',
+ 'doc_path': '/library/logging.html',
+ 'doc_url': 'https://docs.python.org/3.13/library/logging.html',
'expected_output': 'INFO:service started\nWARNING:disk almost full\n',
+ 'explanation': ['`logging` records operational events without using `print` as infrastructure. '
+ 'Loggers name where each event came from, handlers route records to outputs, and '
+ 'levels (`DEBUG`, `INFO`, `WARNING`, `ERROR`) let operators choose how much '
+ 'detail they want to see.',
+ 'Use it when a program needs structured records with thresholds — production '
+ 'services, command-line tools, scheduled jobs. Prefer plain `print` when a small '
+ 'script just needs to show a line of human output.',
+ 'The example wires a single stream handler to stdout to keep the output '
+ 'deterministic. Real applications usually configure logging once at startup and '
+ 'then call `logging.getLogger(__name__)` from each module.'],
+ 'min_python': None,
'notes': ['Loggers name where an event came from.',
'Handlers decide where records go.',
'Levels let operators choose how much detail to see.'],
- 'cells': [{'prose': ['Loggers name where an event came from.'],
- 'code': 'import logging\n'
- 'import sys\n'
+ 'section': 'Standard Library',
+ 'see_also': [],
+ 'slug': 'logging',
+ 'summary': 'logging records operational events without using print as infrastructure.',
+ 'title': 'Logging',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'import logging\n'
+ 'import sys\n'
+ '\n'
+ 'logger = logging.getLogger("example")\n'
+ 'logger.setLevel(logging.INFO)\n'
+ 'handler = logging.StreamHandler(sys.stdout)\n'
+ 'handler.setFormatter(logging.Formatter("%(levelname)s:%(message)s"))\n'
+ 'logger.handlers[:] = [handler]\n'
+ '\n'
+ 'logger.debug("hidden")\n'
+ 'logger.info("service started")\n'
+ 'logger.warning("disk almost full")',
+ 'prose': 'Loggers name where an event came from.'}]},
+ {'cells': [{'code': 'def add(left, right):\n return left + right\n\nprint(add(2, 3))',
+ 'kind': 'cell',
+ 'line': 22,
+ 'output': '5',
+ 'prose': ['A test starts with behavior small enough to name. The function can be '
+ 'ordinary code; the test supplies a representative input and expected '
+ 'result.']},
+ {'code': 'import unittest\n'
'\n'
- 'logger = logging.getLogger("example")\n'
- 'logger.setLevel(logging.INFO)\n'
- 'handler = logging.StreamHandler(sys.stdout)\n'
- 'handler.setFormatter(logging.Formatter("%(levelname)s:%(message)s"))\n'
- 'logger.handlers[:] = [handler]\n'
'\n'
- 'logger.debug("hidden")\n'
- 'logger.info("service started")\n'
- 'logger.warning("disk almost full")',
- 'output': 'INFO:service started\nWARNING:disk almost full',
- 'line': 17,
- 'kind': 'cell'}]},
- {'slug': 'testing',
- 'title': 'Testing',
- 'section': 'Standard Library',
- 'summary': 'Tests make expected behavior executable and repeatable.',
- 'doc_url': 'https://docs.python.org/3.13/library/unittest.html',
+ 'def divide(left, right):\n'
+ ' if right == 0:\n'
+ ' raise ZeroDivisionError("denominator is zero")\n'
+ ' return left / right\n'
+ '\n'
+ '\n'
+ 'class AddTests(unittest.TestCase):\n'
+ ' def setUp(self):\n'
+ ' self.zero = 0\n'
+ '\n'
+ ' def test_adds_numbers(self):\n'
+ ' self.assertEqual(add(self.zero + 2, 3), 5)\n'
+ '\n'
+ ' def test_adds_empty_strings(self):\n'
+ ' self.assertEqual(add("", "py"), "py")\n'
+ '\n'
+ ' def test_divide_by_zero_raises(self):\n'
+ ' with self.assertRaises(ZeroDivisionError):\n'
+ ' divide(1, 0)\n'
+ '\n'
+ 'print([name for name in dir(AddTests) if name.startswith("test_")])',
+ 'kind': 'cell',
+ 'line': 37,
+ 'output': "['test_adds_empty_strings', 'test_adds_numbers', "
+ "'test_divide_by_zero_raises']",
+ 'prose': ['`unittest.TestCase` groups test methods. `setUp` runs before each test '
+ 'method to build per-test fixtures, `assertEqual` checks values, and '
+ '`assertRaises` asserts that a block raises the expected exception type.']},
+ {'code': 'import io\n'
+ '\n'
+ 'suite = unittest.defaultTestLoader.loadTestsFromTestCase(AddTests)\n'
+ 'stream = io.StringIO()\n'
+ 'result = unittest.TextTestRunner(stream=stream, verbosity=0).run(suite)\n'
+ 'print(result.testsRun)\n'
+ 'print(result.wasSuccessful())',
+ 'kind': 'cell',
+ 'line': 72,
+ 'output': '3\nTrue',
+ 'prose': ['A runner executes the suite and records whether every assertion passed. '
+ "Capturing the runner stream keeps this page's output deterministic."]}],
'code': 'import io\n'
'import unittest\n'
'\n'
@@ -4475,89 +9928,109 @@
'def add(left, right):\n'
' return left + right\n'
'\n'
+ '\n'
+ 'def divide(left, right):\n'
+ ' if right == 0:\n'
+ ' raise ZeroDivisionError("denominator is zero")\n'
+ ' return left / right\n'
+ '\n'
+ '\n'
'class AddTests(unittest.TestCase):\n'
+ ' def setUp(self):\n'
+ ' self.zero = 0\n'
+ '\n'
' def test_adds_numbers(self):\n'
- ' self.assertEqual(add(2, 3), 5)\n'
+ ' self.assertEqual(add(self.zero + 2, 3), 5)\n'
'\n'
' def test_adds_empty_strings(self):\n'
' self.assertEqual(add("", "py"), "py")\n'
'\n'
+ ' def test_divide_by_zero_raises(self):\n'
+ ' with self.assertRaises(ZeroDivisionError):\n'
+ ' divide(1, 0)\n'
+ '\n'
'suite = unittest.defaultTestLoader.loadTestsFromTestCase(AddTests)\n'
'stream = io.StringIO()\n'
'result = unittest.TextTestRunner(stream=stream, verbosity=0).run(suite)\n'
'print(result.testsRun)\n'
'print(result.wasSuccessful())\n',
- 'expected_output': '2\nTrue\n',
+ 'doc_path': '/library/unittest.html',
+ 'doc_url': 'https://docs.python.org/3.13/library/unittest.html',
+ 'expected_output': '3\nTrue\n',
+ 'explanation': ['Tests turn expected behavior into code that can be run again. The useful unit '
+ 'is usually a small example of behavior with clear input, action, and assertion.',
+ "Python's `unittest` library provides test cases, assertions, suites, and "
+ 'runners. Projects often use `pytest` for ergonomics, but the same idea remains: '
+ 'a test names behavior and fails when the behavior changes.',
+ 'A broad testing practice also includes fixtures, integration tests, property '
+ 'tests, and coverage. This example stays on the smallest standard-library loop: '
+ 'define behavior, assert the result, run the suite, inspect success.'],
+ 'min_python': None,
'notes': ['Test method names should describe behavior, not implementation details.',
'A good unit test is deterministic and independent of test order.',
- 'Use broader integration tests when the behavior depends on several components working together.'],
- 'cells': [{'prose': ['A test starts with behavior small enough to name. The function can be ordinary code; the test '
- 'supplies a representative input and expected result.'],
- 'code': 'def add(left, right):\n return left + right\n\nprint(add(2, 3))',
- 'output': '5',
- 'line': 17,
- 'kind': 'cell'},
- {'prose': ['`unittest.TestCase` groups test methods. Assertion methods such as `assertEqual` make the '
- 'expected behavior explicit.'],
- 'code': 'import unittest\n'
- '\n'
- 'class AddTests(unittest.TestCase):\n'
- ' def test_adds_numbers(self):\n'
- ' self.assertEqual(add(2, 3), 5)\n'
- '\n'
- ' def test_adds_empty_strings(self):\n'
- ' self.assertEqual(add("", "py"), "py")\n'
- '\n'
- 'print([name for name in dir(AddTests) if name.startswith("test_")])',
- 'output': "['test_adds_empty_strings', 'test_adds_numbers']",
- 'line': 32,
- 'kind': 'cell'},
- {'prose': ['A runner executes the suite and records whether every assertion passed. Capturing the runner '
- "stream keeps this page's output deterministic."],
- 'code': 'import io\n'
- '\n'
- 'suite = unittest.defaultTestLoader.loadTestsFromTestCase(AddTests)\n'
- 'stream = io.StringIO()\n'
- 'result = unittest.TextTestRunner(stream=stream, verbosity=0).run(suite)\n'
- 'print(result.testsRun)\n'
- 'print(result.wasSuccessful())',
- 'output': '2\nTrue',
- 'line': 53,
- 'kind': 'cell'}]},
- {'slug': 'subprocesses',
- 'title': 'Subprocesses',
+ 'Use broader integration tests when the behavior depends on several components working '
+ 'together.'],
'section': 'Standard Library',
- 'summary': 'subprocess runs external commands with explicit arguments and captured outputs.',
- 'doc_url': 'https://docs.python.org/3.13/library/subprocess.html',
- 'code': 'import subprocess\n'
- 'import sys\n'
- '\n'
- 'result = subprocess.run(\n'
- ' [sys.executable, "-c", "print(\'child process\')"],\n'
- ' text=True,\n'
- ' capture_output=True,\n'
- ' check=True,\n'
- ')\n'
- '\n'
- 'print(result.stdout.strip())\n'
- 'print(result.returncode)\n',
- 'expected_output': 'child process\n0\n',
- 'notes': ['Use a list of arguments instead of shell strings when possible.',
- 'Capture output when the parent program needs to inspect it.',
- '`check=True` turns non-zero exits into exceptions.'],
- 'cells': [{'prose': ['Dynamic Workers do not provide child processes.'],
- 'code': 'result = subprocess.run(\n'
+ 'see_also': ['assertions', 'exceptions', 'modules'],
+ 'slug': 'testing',
+ 'summary': 'Tests make expected behavior executable and repeatable.',
+ 'title': 'Testing',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'def add(left, right):\n return left + right\n\nprint(add(2, 3))',
+ 'prose': 'A test starts with behavior small enough to name. The function can be '
+ 'ordinary code; the test supplies a representative input and expected '
+ 'result.'},
+ {'code': 'import unittest\n'
+ '\n'
+ '\n'
+ 'def divide(left, right):\n'
+ ' if right == 0:\n'
+ ' raise ZeroDivisionError("denominator is zero")\n'
+ ' return left / right\n'
+ '\n'
+ '\n'
+ 'class AddTests(unittest.TestCase):\n'
+ ' def setUp(self):\n'
+ ' self.zero = 0\n'
+ '\n'
+ ' def test_adds_numbers(self):\n'
+ ' self.assertEqual(add(self.zero + 2, 3), 5)\n'
+ '\n'
+ ' def test_adds_empty_strings(self):\n'
+ ' self.assertEqual(add("", "py"), "py")\n'
+ '\n'
+ ' def test_divide_by_zero_raises(self):\n'
+ ' with self.assertRaises(ZeroDivisionError):\n'
+ ' divide(1, 0)\n'
+ '\n'
+ 'print([name for name in dir(AddTests) if name.startswith("test_")])',
+ 'prose': '`unittest.TestCase` groups test methods. `setUp` runs before each '
+ 'test method to build per-test fixtures, `assertEqual` checks values, '
+ 'and `assertRaises` asserts that a block raises the expected exception '
+ 'type.'},
+ {'code': 'import io\n'
+ '\n'
+ 'suite = unittest.defaultTestLoader.loadTestsFromTestCase(AddTests)\n'
+ 'stream = io.StringIO()\n'
+ 'result = unittest.TextTestRunner(stream=stream, '
+ 'verbosity=0).run(suite)\n'
+ 'print(result.testsRun)\n'
+ 'print(result.wasSuccessful())',
+ 'prose': 'A runner executes the suite and records whether every assertion '
+ "passed. Capturing the runner stream keeps this page's output "
+ 'deterministic.'}]},
+ {'cells': [{'code': 'result = subprocess.run(\n'
' [sys.executable, "-c", "print(\'child process\')"],\n'
' text=True,\n'
' capture_output=True,\n'
' check=True,\n'
')',
- 'output': '',
+ 'kind': 'unsupported',
'line': 18,
- 'kind': 'unsupported'},
- {'prose': ['`subprocess.run()` starts a child process and waits for it. `capture_output=True` stores the '
- "child's standard output and error streams on the result object."],
- 'code': 'import subprocess\n'
+ 'output': '',
+ 'prose': ['Dynamic Workers do not provide child processes.']},
+ {'code': 'import subprocess\n'
'import sys\n'
'\n'
'result = subprocess.run(\n'
@@ -4569,40 +10042,74 @@
'\n'
'print(result.stdout.strip())\n'
'print(result.returncode)',
- 'output': 'child process\n0',
+ 'kind': 'cell',
'line': 31,
- 'kind': 'cell'}]},
- {'slug': 'threads-and-processes',
- 'title': 'Threads and Processes',
- 'section': 'Standard Library',
- 'summary': 'Threads share memory, while processes run in separate interpreters.',
- 'doc_url': 'https://docs.python.org/3.13/library/concurrent.futures.html',
- 'code': 'from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor\n'
- '\n'
- '\n'
- 'def square(number):\n'
- ' return number * number\n'
+ 'output': 'child process\n0',
+ 'prose': ['`subprocess.run()` starts a child process and waits for it. '
+ "`capture_output=True` stores the child's standard output and error streams "
+ 'on the result object.']}],
+ 'code': 'import subprocess\n'
+ 'import sys\n'
'\n'
- 'with ThreadPoolExecutor(max_workers=2) as pool:\n'
- ' print(list(pool.map(square, [1, 2, 3])))\n'
+ 'result = subprocess.run(\n'
+ ' [sys.executable, "-c", "print(\'child process\')"],\n'
+ ' text=True,\n'
+ ' capture_output=True,\n'
+ ' check=True,\n'
+ ')\n'
'\n'
- 'print(ProcessPoolExecutor.__name__)\n',
- 'expected_output': '[1, 4, 9]\nProcessPoolExecutor\n',
- 'notes': ['Threads share memory, so mutable shared state needs care.',
- 'Processes avoid shared interpreter state but require values to cross a process boundary.',
- 'Prefer `asyncio` for coroutine-based I/O and executors for ordinary blocking callables.'],
- 'cells': [{'prose': ['Dynamic Workers do not provide native threads or child processes.'],
- 'code': 'with ThreadPoolExecutor(max_workers=2) as pool:\n'
+ 'print(result.stdout.strip())\n'
+ 'print(result.returncode)\n',
+ 'doc_path': '/library/subprocess.html',
+ 'doc_url': 'https://docs.python.org/3.13/library/subprocess.html',
+ 'expected_output': 'child process\n0\n',
+ 'explanation': ['`subprocess` is the standard boundary for running external commands. It starts '
+ 'another program, waits for it, and gives you a result object with the exit code '
+ 'and captured output.',
+ 'Use a list of arguments when possible, capture output when the parent program '
+ 'needs to inspect it, and treat a non-zero return code as a failure. The same '
+ 'ideas apply whether the child program is Python, Git, a compiler, or another '
+ 'command-line tool.',
+ 'The important boundary is between Python objects and the operating system '
+ 'process table. Python prepares arguments and environment, then the child '
+ 'program runs independently and reports back through streams and an exit '
+ 'status.'],
+ 'min_python': None,
+ 'notes': ['Use a list of arguments instead of shell strings when possible.',
+ 'Capture output when the parent program needs to inspect it.',
+ '`check=True` turns non-zero exits into exceptions.'],
+ 'section': 'Standard Library',
+ 'see_also': [],
+ 'slug': 'subprocesses',
+ 'summary': 'subprocess runs external commands with explicit arguments and captured outputs.',
+ 'title': 'Subprocesses',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'import subprocess\n'
+ 'import sys\n'
+ '\n'
+ 'result = subprocess.run(\n'
+ ' [sys.executable, "-c", "print(\'child process\')"],\n'
+ ' text=True,\n'
+ ' capture_output=True,\n'
+ ' check=True,\n'
+ ')\n'
+ '\n'
+ 'print(result.stdout.strip())\n'
+ 'print(result.returncode)',
+ 'prose': '`subprocess.run()` starts a child process and waits for it. '
+ "`capture_output=True` stores the child's standard output and error "
+ 'streams on the result object.'}]},
+ {'cells': [{'code': 'with ThreadPoolExecutor(max_workers=2) as pool:\n'
' print(list(pool.map(square, [1, 2, 3])))\n'
'\n'
'with ProcessPoolExecutor(max_workers=2) as pool:\n'
' print(list(pool.map(pow, [4, 5], [2, 2])))',
- 'output': '',
+ 'kind': 'unsupported',
'line': 18,
- 'kind': 'unsupported'},
- {'prose': ['A thread pool runs ordinary callables while sharing memory with the current process. `map()` '
- 'returns results in input order.'],
- 'code': 'from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor\n'
+ 'output': '',
+ 'prose': ['Dynamic Workers do not provide native threads or child processes.']},
+ {'code': 'from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor\n'
'\n'
'\n'
'def square(number):\n'
@@ -4610,20 +10117,92 @@
'\n'
'with ThreadPoolExecutor(max_workers=2) as pool:\n'
' print(list(pool.map(square, [1, 2, 3])))',
- 'output': '[1, 4, 9]',
+ 'kind': 'cell',
'line': 30,
- 'kind': 'cell'},
- {'prose': ['A process pool uses separate Python processes. That boundary is heavier, but it can run '
- 'CPU-bound work outside the current interpreter.'],
- 'code': 'print(ProcessPoolExecutor.__name__)',
- 'output': 'ProcessPoolExecutor',
+ 'output': '[1, 4, 9]',
+ 'prose': ['A thread pool runs ordinary callables while sharing memory with the '
+ 'current process. `map()` returns results in input order.']},
+ {'code': 'print(ProcessPoolExecutor.__name__)',
+ 'kind': 'cell',
'line': 49,
- 'kind': 'cell'}]},
- {'slug': 'networking',
- 'title': 'Networking',
+ 'output': 'ProcessPoolExecutor',
+ 'prose': ['A process pool uses separate Python processes. That boundary is heavier, '
+ 'but it can run CPU-bound work outside the current interpreter.']}],
+ 'code': 'from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor\n'
+ '\n'
+ '\n'
+ 'def square(number):\n'
+ ' return number * number\n'
+ '\n'
+ 'with ThreadPoolExecutor(max_workers=2) as pool:\n'
+ ' print(list(pool.map(square, [1, 2, 3])))\n'
+ '\n'
+ 'print(ProcessPoolExecutor.__name__)\n',
+ 'doc_path': '/library/concurrent.futures.html',
+ 'doc_url': 'https://docs.python.org/3.13/library/concurrent.futures.html',
+ 'expected_output': '[1, 4, 9]\nProcessPoolExecutor\n',
+ 'explanation': ['Threads and processes are two ways to run work outside the current control '
+ 'path. Threads are useful for overlapping I/O-shaped waits, while processes are '
+ 'useful when CPU-bound work needs separate interpreter processes.',
+ 'This is different from `asyncio`: threads and processes run callables through '
+ 'executors, while `async` code cooperatively awaits coroutines. Choose the '
+ 'smallest concurrency model that matches the bottleneck.',
+ 'The executor interface lets callers submit ordinary functions without '
+ 'committing the rest of the code to one scheduling strategy. That makes it '
+ 'easier to compare thread and process boundaries at the call site.'],
+ 'min_python': None,
+ 'notes': ['Threads share memory, so mutable shared state needs care.',
+ 'Processes avoid shared interpreter state but require values to cross a process '
+ 'boundary.',
+ 'Prefer `asyncio` for coroutine-based I/O and executors for ordinary blocking '
+ 'callables.'],
'section': 'Standard Library',
- 'summary': 'Networking code exchanges bytes across explicit protocol boundaries.',
- 'doc_url': 'https://docs.python.org/3.13/library/socket.html',
+ 'see_also': [],
+ 'slug': 'threads-and-processes',
+ 'summary': 'Threads share memory, while processes run in separate interpreters.',
+ 'title': 'Threads and Processes',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'from concurrent.futures import ProcessPoolExecutor, '
+ 'ThreadPoolExecutor\n'
+ '\n'
+ '\n'
+ 'def square(number):\n'
+ ' return number * number\n'
+ '\n'
+ 'with ThreadPoolExecutor(max_workers=2) as pool:\n'
+ ' print(list(pool.map(square, [1, 2, 3])))',
+ 'prose': 'A thread pool runs ordinary callables while sharing memory with the '
+ 'current process. `map()` returns results in input order.'},
+ {'code': 'print(ProcessPoolExecutor.__name__)',
+ 'prose': 'A process pool uses separate Python processes. That boundary is '
+ 'heavier, but it can run CPU-bound work outside the current '
+ 'interpreter.'}]},
+ {'cells': [{'code': 'left, right = socket.socketpair()\n'
+ 'left.sendall("ping".encode("utf-8"))\n'
+ 'data = right.recv(16)',
+ 'kind': 'unsupported',
+ 'line': 18,
+ 'output': '',
+ 'prose': ['Dynamic Workers do not provide arbitrary low-level sockets, and this app '
+ 'disables Dynamic Worker outbound access.']},
+ {'code': 'import socket\n'
+ '\n'
+ 'left, right = socket.socketpair()\n'
+ 'try:\n'
+ ' message = "ping"\n'
+ ' left.sendall(message.encode("utf-8"))\n'
+ ' data = right.recv(16)\n'
+ ' print(data)\n'
+ ' print(data.decode("utf-8"))\n'
+ 'finally:\n'
+ ' left.close()\n'
+ ' right.close()',
+ 'kind': 'cell',
+ 'line': 28,
+ 'output': "b'ping'\nping",
+ 'prose': ['Sockets exchange bytes. Encoding and decoding make the boundary between '
+ 'Python text and network data visible.']}],
'code': 'import socket\n'
'\n'
'left, right = socket.socketpair()\n'
@@ -4636,38 +10215,78 @@
'finally:\n'
' left.close()\n'
' right.close()\n',
+ 'doc_path': '/library/socket.html',
+ 'doc_url': 'https://docs.python.org/3.13/library/socket.html',
'expected_output': "b'ping'\nping\n",
+ 'explanation': ['Networking code sends and receives bytes. Higher-level HTTP clients hide many '
+ 'details, but the core boundary is still explicit: text must be encoded before '
+ 'sending and decoded after receiving.',
+ 'This example uses `socket.socketpair()` so it stays local and deterministic in '
+ 'ordinary Python. Real network clients often use `socket.create_connection()` or '
+ 'a higher-level HTTP library, but the same byte boundary applies.',
+ 'The useful mental model is endpoint plus bytes plus cleanup. A socket connects '
+ 'two endpoints, transfers byte strings, and must be closed when the conversation '
+ 'is finished.'],
+ 'min_python': None,
'notes': ['Network protocols move bytes, not Python `str` objects.',
'Close real sockets when finished, usually with a context manager or `finally` block.',
- 'Use high-level HTTP libraries for application HTTP unless socket-level control is the lesson.'],
- 'cells': [{'prose': ['Dynamic Workers do not provide arbitrary low-level sockets, and this app disables Dynamic '
- 'Worker outbound access.'],
- 'code': 'left, right = socket.socketpair()\nleft.sendall("ping".encode("utf-8"))\ndata = right.recv(16)',
- 'output': '',
- 'line': 18,
- 'kind': 'unsupported'},
- {'prose': ['Sockets exchange bytes. Encoding and decoding make the boundary between Python text and '
- 'network data visible.'],
- 'code': 'import socket\n'
- '\n'
- 'left, right = socket.socketpair()\n'
- 'try:\n'
- ' message = "ping"\n'
- ' left.sendall(message.encode("utf-8"))\n'
- ' data = right.recv(16)\n'
- ' print(data)\n'
- ' print(data.decode("utf-8"))\n'
- 'finally:\n'
- ' left.close()\n'
- ' right.close()',
- 'output': "b'ping'\nping",
- 'line': 28,
- 'kind': 'cell'}]},
- {'slug': 'datetime',
- 'title': 'Dates and Times',
+ 'Use high-level HTTP libraries for application HTTP unless socket-level control is the '
+ 'lesson.'],
'section': 'Standard Library',
- 'summary': 'datetime represents dates, times, durations, formatting, and parsing.',
- 'doc_url': 'https://docs.python.org/3.13/library/datetime.html',
+ 'see_also': [],
+ 'slug': 'networking',
+ 'summary': 'Networking code exchanges bytes across explicit protocol boundaries.',
+ 'title': 'Networking',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'import socket\n'
+ '\n'
+ 'left, right = socket.socketpair()\n'
+ 'try:\n'
+ ' message = "ping"\n'
+ ' left.sendall(message.encode("utf-8"))\n'
+ ' data = right.recv(16)\n'
+ ' print(data)\n'
+ ' print(data.decode("utf-8"))\n'
+ 'finally:\n'
+ ' left.close()\n'
+ ' right.close()',
+ 'prose': 'Sockets exchange bytes. Encoding and decoding make the boundary '
+ 'between Python text and network data visible.'}]},
+ {'cells': [{'code': 'from datetime import date, datetime, time, timedelta, timezone\n'
+ '\n'
+ 'release_day = date(2026, 5, 4)\n'
+ 'meeting_time = time(12, 30)\n'
+ 'created_at = datetime.combine(release_day, meeting_time, '
+ 'tzinfo=timezone.utc)\n'
+ '\n'
+ 'print(release_day.isoformat())\n'
+ 'print(meeting_time.isoformat())\n'
+ 'print(created_at.isoformat())',
+ 'kind': 'cell',
+ 'line': 17,
+ 'output': '2026-05-04\n12:30:00\n2026-05-04T12:30:00+00:00',
+ 'prose': ['The `datetime` module separates calendar dates, clock times, combined '
+ 'datetimes, and durations. Import the types you need explicitly.',
+ 'Use `date` for a calendar day and `time` for a time of day. Combine them '
+ 'into a timezone-aware `datetime` when you mean an instant.',
+ '`isoformat()` produces stable machine-readable text. It is a good default '
+ 'for examples, APIs, and logs.']},
+ {'code': 'expires_at = created_at + timedelta(days=7, hours=2)\n'
+ 'print(expires_at.isoformat())',
+ 'kind': 'cell',
+ 'line': 43,
+ 'output': '2026-05-11T14:30:00+00:00',
+ 'prose': ['Use `timedelta` for durations. Adding one to a `datetime` produces another '
+ '`datetime` without manually changing calendar fields.']},
+ {'code': 'print(created_at.strftime("%Y-%m-%d %H:%M %Z"))\n'
+ 'parsed = datetime.fromisoformat("2026-05-04T12:30:00+00:00")\n'
+ 'print(parsed == created_at)',
+ 'kind': 'cell',
+ 'line': 56,
+ 'output': '2026-05-04 12:30 UTC\nTrue',
+ 'prose': ['Use `strftime()` for human-facing formatting and `fromisoformat()` when '
+ 'reading ISO 8601 text back into a `datetime`.']}],
'code': 'from datetime import date, datetime, time, timedelta, timezone\n'
'\n'
'release_day = date(2026, 5, 4)\n'
@@ -4684,52 +10303,97 @@
'print(created_at.strftime("%Y-%m-%d %H:%M %Z"))\n'
'parsed = datetime.fromisoformat("2026-05-04T12:30:00+00:00")\n'
'print(parsed == created_at)\n',
+ 'doc_path': '/library/datetime.html',
+ 'doc_url': 'https://docs.python.org/3.13/library/datetime.html',
'expected_output': '2026-05-04\n'
'12:30:00\n'
'2026-05-04T12:30:00+00:00\n'
'2026-05-11T14:30:00+00:00\n'
'2026-05-04 12:30 UTC\n'
'True\n',
+ 'explanation': ['The `datetime` module covers several related ideas: `date` for calendar days, '
+ '`time` for clock times, `datetime` for both together, and `timedelta` for '
+ 'durations.',
+ 'Timezone-aware datetimes avoid ambiguity in real systems. `timezone.utc` is a '
+ 'clear default for examples because output stays stable and portable.',
+ 'Use ISO formatting for interchange, `strftime()` for display, and parsing '
+ 'helpers such as `fromisoformat()` to turn text back into datetime objects.'],
+ 'min_python': None,
'notes': ['Use timezone-aware datetimes for instants that cross system or user boundaries.',
- 'Use `date` for calendar days, `time` for clock times, `datetime` for both, and `timedelta` for durations.',
+ 'Use `date` for calendar days, `time` for clock times, `datetime` for both, and '
+ '`timedelta` for durations.',
'Prefer ISO 8601 strings for interchange; use `strftime` for human-facing display.'],
- 'cells': [{'prose': ['The `datetime` module separates calendar dates, clock times, combined datetimes, and '
- 'durations. Import the types you need explicitly.',
- 'Use `date` for a calendar day and `time` for a time of day. Combine them into a timezone-aware '
- '`datetime` when you mean an instant.',
- '`isoformat()` produces stable machine-readable text. It is a good default for examples, APIs, '
- 'and logs.'],
- 'code': 'from datetime import date, datetime, time, timedelta, timezone\n'
+ 'section': 'Standard Library',
+ 'see_also': [],
+ 'slug': 'datetime',
+ 'summary': 'datetime represents dates, times, durations, formatting, and parsing.',
+ 'title': 'Dates and Times',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'from datetime import date, datetime, time, timedelta, timezone\n'
+ '\n'
+ 'release_day = date(2026, 5, 4)\n'
+ 'meeting_time = time(12, 30)\n'
+ 'created_at = datetime.combine(release_day, meeting_time, '
+ 'tzinfo=timezone.utc)\n'
+ '\n'
+ 'print(release_day.isoformat())\n'
+ 'print(meeting_time.isoformat())\n'
+ 'print(created_at.isoformat())',
+ 'prose': 'The `datetime` module separates calendar dates, clock times, combined '
+ 'datetimes, and durations. Import the types you need explicitly.'},
+ {'code': 'from datetime import date, datetime, time, timedelta, timezone\n'
+ '\n'
+ 'release_day = date(2026, 5, 4)\n'
+ 'meeting_time = time(12, 30)\n'
+ 'created_at = datetime.combine(release_day, meeting_time, '
+ 'tzinfo=timezone.utc)\n'
+ '\n'
+ 'print(release_day.isoformat())\n'
+ 'print(meeting_time.isoformat())\n'
+ 'print(created_at.isoformat())',
+ 'prose': 'Use `date` for a calendar day and `time` for a time of day. Combine '
+ 'them into a timezone-aware `datetime` when you mean an instant.'},
+ {'code': 'from datetime import date, datetime, time, timedelta, timezone\n'
+ '\n'
+ 'release_day = date(2026, 5, 4)\n'
+ 'meeting_time = time(12, 30)\n'
+ 'created_at = datetime.combine(release_day, meeting_time, '
+ 'tzinfo=timezone.utc)\n'
+ '\n'
+ 'print(release_day.isoformat())\n'
+ 'print(meeting_time.isoformat())\n'
+ 'print(created_at.isoformat())',
+ 'prose': '`isoformat()` produces stable machine-readable text. It is a good '
+ 'default for examples, APIs, and logs.'},
+ {'code': 'expires_at = created_at + timedelta(days=7, hours=2)\n'
+ 'print(expires_at.isoformat())',
+ 'prose': 'Use `timedelta` for durations. Adding one to a `datetime` produces '
+ 'another `datetime` without manually changing calendar fields.'},
+ {'code': 'print(created_at.strftime("%Y-%m-%d %H:%M %Z"))\n'
+ 'parsed = datetime.fromisoformat("2026-05-04T12:30:00+00:00")\n'
+ 'print(parsed == created_at)',
+ 'prose': 'Use `strftime()` for human-facing formatting and `fromisoformat()` '
+ 'when reading ISO 8601 text back into a `datetime`.'}]},
+ {'cells': [{'code': 'import csv\n'
+ 'import io\n'
'\n'
- 'release_day = date(2026, 5, 4)\n'
- 'meeting_time = time(12, 30)\n'
- 'created_at = datetime.combine(release_day, meeting_time, tzinfo=timezone.utc)\n'
+ 'text = "name,score\\nAda,98\\nGrace,95\\n"\n'
+ 'reader = csv.DictReader(io.StringIO(text))\n'
+ 'rows = list(reader)\n'
'\n'
- 'print(release_day.isoformat())\n'
- 'print(meeting_time.isoformat())\n'
- 'print(created_at.isoformat())',
- 'output': '2026-05-04\n12:30:00\n2026-05-04T12:30:00+00:00',
+ 'print(rows[0]["name"])\n'
+ 'print(sum(int(row["score"]) for row in rows))\n'
+ '\n'
+ 'output = io.StringIO()\n'
+ 'writer = csv.DictWriter(output, fieldnames=["name", "passed"])\n'
+ 'writer.writeheader()\n'
+ 'writer.writerow({"name": "Ada", "passed": True})\n'
+ 'print(output.getvalue().splitlines()[1])',
+ 'kind': 'cell',
'line': 17,
- 'kind': 'cell'},
- {'prose': ['Use `timedelta` for durations. Adding one to a `datetime` produces another `datetime` without '
- 'manually changing calendar fields.'],
- 'code': 'expires_at = created_at + timedelta(days=7, hours=2)\nprint(expires_at.isoformat())',
- 'output': '2026-05-11T14:30:00+00:00',
- 'line': 43,
- 'kind': 'cell'},
- {'prose': ['Use `strftime()` for human-facing formatting and `fromisoformat()` when reading ISO 8601 text '
- 'back into a `datetime`.'],
- 'code': 'print(created_at.strftime("%Y-%m-%d %H:%M %Z"))\n'
- 'parsed = datetime.fromisoformat("2026-05-04T12:30:00+00:00")\n'
- 'print(parsed == created_at)',
- 'output': '2026-05-04 12:30 UTC\nTrue',
- 'line': 56,
- 'kind': 'cell'}]},
- {'slug': 'csv-data',
- 'title': 'CSV Data',
- 'section': 'Standard Library',
- 'summary': 'csv reads and writes row-shaped text data.',
- 'doc_url': 'https://docs.python.org/3.13/library/csv.html',
+ 'output': 'Ada\n193\nAda,True',
+ 'prose': ['Use `DictReader` when column names should become dictionary keys.']}],
'code': 'import csv\n'
'import io\n'
'\n'
@@ -4745,55 +10409,45 @@
'writer.writeheader()\n'
'writer.writerow({"name": "Ada", "passed": True})\n'
'print(output.getvalue().splitlines()[1])\n',
+ 'doc_path': '/library/csv.html',
+ 'doc_url': 'https://docs.python.org/3.13/library/csv.html',
'expected_output': 'Ada\n193\nAda,True\n',
+ 'explanation': ['csv reads and writes row-shaped text data. It exists to make a common boundary '
+ 'explicit instead of leaving the behavior implicit in a larger program.',
+ 'Use it when the problem shape matches the example, and prefer simpler '
+ 'neighboring tools when the extra machinery would hide the intent. The notes '
+ 'call out the boundary so the feature stays practical rather than decorative.',
+ 'The example is small, deterministic, and focused on the semantic point. The '
+ 'complete source is editable below, while the walkthrough pairs the source with '
+ 'its output.'],
+ 'min_python': None,
'notes': ['Use `DictReader` when column names should become dictionary keys.',
'CSV fields arrive as text, so convert numbers explicitly.',
'`DictWriter` writes dictionaries back to row-shaped text.'],
- 'cells': [{'prose': ['Use `DictReader` when column names should become dictionary keys.'],
- 'code': 'import csv\n'
- 'import io\n'
- '\n'
- 'text = "name,score\\nAda,98\\nGrace,95\\n"\n'
- 'reader = csv.DictReader(io.StringIO(text))\n'
- 'rows = list(reader)\n'
- '\n'
- 'print(rows[0]["name"])\n'
- 'print(sum(int(row["score"]) for row in rows))\n'
- '\n'
- 'output = io.StringIO()\n'
- 'writer = csv.DictWriter(output, fieldnames=["name", "passed"])\n'
- 'writer.writeheader()\n'
- 'writer.writerow({"name": "Ada", "passed": True})\n'
- 'print(output.getvalue().splitlines()[1])',
- 'output': 'Ada\n193\nAda,True',
- 'line': 17,
- 'kind': 'cell'}]},
- {'slug': 'async-await',
- 'title': 'Async Await',
- 'section': 'Async',
- 'summary': 'async def creates coroutines, and await pauses until awaitable work completes.',
- 'doc_url': 'https://docs.python.org/3.13/library/asyncio-task.html',
- 'code': 'import asyncio\n'
- '\n'
- 'async def fetch_title(slug):\n'
- ' await asyncio.sleep(0)\n'
- ' return slug.replace("-", " ").title()\n'
- '\n'
- 'async def main():\n'
- ' title = await fetch_title("async-await")\n'
- ' print(title)\n'
- ' titles = await asyncio.gather(fetch_title("json"), fetch_title("datetime"))\n'
- ' print(titles)\n'
- '\n'
- 'asyncio.run(main())\n',
- 'expected_output': "Async Await\n['Json', 'Datetime']\n",
- 'notes': ['Calling an async function creates a coroutine object.',
- '`await` yields control until an awaitable completes.',
- 'Workers request handlers are async, so this pattern appears around fetches and bindings.',
- 'Prefer ordinary functions when there is no awaitable work to coordinate.'],
- 'cells': [{'prose': ['An `async def` function returns a coroutine object when called. The function body has not '
- 'produced its final result yet.'],
- 'code': 'import asyncio\n'
+ 'section': 'Standard Library',
+ 'see_also': [],
+ 'slug': 'csv-data',
+ 'summary': 'csv reads and writes row-shaped text data.',
+ 'title': 'CSV Data',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'import csv\n'
+ 'import io\n'
+ '\n'
+ 'text = "name,score\\nAda,98\\nGrace,95\\n"\n'
+ 'reader = csv.DictReader(io.StringIO(text))\n'
+ 'rows = list(reader)\n'
+ '\n'
+ 'print(rows[0]["name"])\n'
+ 'print(sum(int(row["score"]) for row in rows))\n'
+ '\n'
+ 'output = io.StringIO()\n'
+ 'writer = csv.DictWriter(output, fieldnames=["name", "passed"])\n'
+ 'writer.writeheader()\n'
+ 'writer.writerow({"name": "Ada", "passed": True})\n'
+ 'print(output.getvalue().splitlines()[1])',
+ 'prose': 'Use `DictReader` when column names should become dictionary keys.'}]},
+ {'cells': [{'code': 'import asyncio\n'
'\n'
'async def fetch_title(slug):\n'
' await asyncio.sleep(0)\n'
@@ -4802,62 +10456,183 @@
'coroutine = fetch_title("async-await")\n'
'print(coroutine.__class__.__name__)\n'
'coroutine.close()',
+ 'kind': 'cell',
+ 'line': 24,
'output': 'coroutine',
- 'line': 19,
- 'kind': 'cell'},
- {'prose': ['Use `await` inside another coroutine to get the eventual result. `asyncio.run()` starts an '
- 'event loop for the top-level coroutine.'],
- 'code': 'async def main():\n'
+ 'prose': ['An `async def` function returns a coroutine object when called. The '
+ 'function body has not produced its final result yet.']},
+ {'code': 'async def main():\n'
' title = await fetch_title("async-await")\n'
' print(title)\n'
'\n'
'asyncio.run(main())',
+ 'kind': 'cell',
+ 'line': 44,
'output': 'Async Await',
- 'line': 39,
- 'kind': 'cell'},
- {'prose': ['`asyncio.gather()` awaits several awaitables and returns their results in order. This is the '
- 'shape used when independent I/O operations can progress together.'],
- 'code': 'async def main():\n'
- ' titles = await asyncio.gather(fetch_title("json"), fetch_title("datetime"))\n'
+ 'prose': ['Use `await` inside another coroutine to get the eventual result. '
+ '`asyncio.run()` starts an event loop for the top-level coroutine.']},
+ {'code': 'async def main():\n'
+ ' titles = await asyncio.gather(fetch_title("json"), '
+ 'fetch_title("datetime"))\n'
' print(titles)\n'
'\n'
'asyncio.run(main())',
+ 'kind': 'cell',
+ 'line': 60,
'output': "['Json', 'Datetime']",
- 'line': 55,
- 'kind': 'cell'}]},
- {'slug': 'async-iteration-and-context',
- 'title': 'Async Iteration and Context',
- 'section': 'Async',
- 'summary': 'async for and async with consume asynchronous streams and cleanup protocols.',
- 'doc_url': 'https://docs.python.org/3.13/reference/compound_stmts.html#async-for',
+ 'prose': ['`asyncio.gather()` awaits several awaitables and returns their results in '
+ 'order. This is the shape used when independent I/O operations can progress '
+ 'together.']},
+ {'code': 'class Session:\n'
+ ' async def __aenter__(self):\n'
+ ' print("open")\n'
+ ' return self\n'
+ '\n'
+ ' async def __aexit__(self, *_):\n'
+ ' print("close")\n'
+ ' return False\n'
+ '\n'
+ '\n'
+ 'async def stream():\n'
+ ' for slug in ["json", "datetime"]:\n'
+ ' await asyncio.sleep(0)\n'
+ ' yield slug\n'
+ '\n'
+ '\n'
+ 'async def driver():\n'
+ ' async with Session():\n'
+ ' async for slug in stream():\n'
+ ' print(slug)\n'
+ '\n'
+ 'asyncio.run(driver())',
+ 'kind': 'cell',
+ 'line': 76,
+ 'output': 'open\njson\ndatetime\nclose',
+ 'prose': ['`async with` and `async for` are the asynchronous forms of context '
+ 'managers and iteration. A class implements `__aenter__`/`__aexit__` to act '
+ 'as an async context manager; an `async def` function with `yield` becomes '
+ 'an async generator. The dedicated [async iteration and '
+ 'context](/iteration/async-iteration-and-context) page explains the '
+ 'protocols in depth.']}],
'code': 'import asyncio\n'
'\n'
- 'async def titles():\n'
- ' for slug in ["values", "async-await"]:\n'
- ' await asyncio.sleep(0)\n'
- ' yield slug.replace("-", " ").title()\n'
+ 'async def fetch_title(slug):\n'
+ ' await asyncio.sleep(0)\n'
+ ' return slug.replace("-", " ").title()\n'
+ '\n'
+ 'async def main():\n'
+ ' title = await fetch_title("async-await")\n'
+ ' print(title)\n'
+ ' titles = await asyncio.gather(fetch_title("json"), fetch_title("datetime"))\n'
+ ' print(titles)\n'
+ '\n'
+ 'asyncio.run(main())\n'
+ '\n'
'\n'
'class Session:\n'
' async def __aenter__(self):\n'
' print("open")\n'
' return self\n'
'\n'
- ' async def __aexit__(self, exc_type, exc, tb):\n'
+ ' async def __aexit__(self, *_):\n'
' print("close")\n'
+ ' return False\n'
'\n'
- 'async def main():\n'
+ '\n'
+ 'async def stream():\n'
+ ' for slug in ["json", "datetime"]:\n'
+ ' await asyncio.sleep(0)\n'
+ ' yield slug\n'
+ '\n'
+ '\n'
+ 'async def driver():\n'
' async with Session():\n'
- ' async for title in titles():\n'
- ' print(title)\n'
+ ' async for slug in stream():\n'
+ ' print(slug)\n'
'\n'
- 'asyncio.run(main())\n',
- 'expected_output': 'open\nValues\nAsync Await\nclose\n',
- 'notes': ['`async for` consumes asynchronous iterators.',
- '`async with` awaits asynchronous setup and cleanup.',
- 'These forms are common around I/O-shaped resources.'],
- 'cells': [{'prose': ['An async generator can `await` before yielding each value. `async for` consumes those values '
- 'with the asynchronous iteration protocol.'],
- 'code': 'import asyncio\n'
+ 'asyncio.run(driver())\n',
+ 'doc_path': '/library/asyncio-task.html',
+ 'doc_url': 'https://docs.python.org/3.13/library/asyncio-task.html',
+ 'expected_output': "Async Await\n['Json', 'Datetime']\nopen\njson\ndatetime\nclose\n",
+ 'explanation': ['`async def` creates a coroutine function. Calling it creates a coroutine '
+ 'object; the body runs when an event loop awaits or schedules it.',
+ '`await` pauses the current coroutine until another awaitable completes. This '
+ 'lets one event loop make progress on other work while a task waits for I/O.',
+ 'Cloudflare Workers handlers are asynchronous, so understanding `await` is '
+ 'practical for fetch calls, bindings, and service interactions even when a small '
+ 'example uses `asyncio.sleep(0)` as a stand-in.',
+ 'The alternative is ordinary `def` for work that completes immediately. Use '
+ 'async code for I/O-shaped waiting, not as a faster replacement for CPU-bound '
+ 'Python.'],
+ 'min_python': None,
+ 'notes': ['Calling an async function creates a coroutine object.',
+ '`await` yields control until an awaitable completes.',
+ 'Workers request handlers are async, so this pattern appears around fetches and '
+ 'bindings.',
+ 'Prefer ordinary functions when there is no awaitable work to coordinate.'],
+ 'section': 'Async',
+ 'see_also': ['async-iteration-and-context', 'functions', 'context-managers'],
+ 'slug': 'async-await',
+ 'summary': 'async def creates coroutines, and await pauses until awaitable work completes.',
+ 'title': 'Async Await',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'import asyncio\n'
+ '\n'
+ 'async def fetch_title(slug):\n'
+ ' await asyncio.sleep(0)\n'
+ ' return slug.replace("-", " ").title()\n'
+ '\n'
+ 'coroutine = fetch_title("async-await")\n'
+ 'print(coroutine.__class__.__name__)\n'
+ 'coroutine.close()',
+ 'prose': 'An `async def` function returns a coroutine object when called. The '
+ 'function body has not produced its final result yet.'},
+ {'code': 'async def main():\n'
+ ' title = await fetch_title("async-await")\n'
+ ' print(title)\n'
+ '\n'
+ 'asyncio.run(main())',
+ 'prose': 'Use `await` inside another coroutine to get the eventual result. '
+ '`asyncio.run()` starts an event loop for the top-level coroutine.'},
+ {'code': 'async def main():\n'
+ ' titles = await asyncio.gather(fetch_title("json"), '
+ 'fetch_title("datetime"))\n'
+ ' print(titles)\n'
+ '\n'
+ 'asyncio.run(main())',
+ 'prose': '`asyncio.gather()` awaits several awaitables and returns their '
+ 'results in order. This is the shape used when independent I/O '
+ 'operations can progress together.'},
+ {'code': 'class Session:\n'
+ ' async def __aenter__(self):\n'
+ ' print("open")\n'
+ ' return self\n'
+ '\n'
+ ' async def __aexit__(self, *_):\n'
+ ' print("close")\n'
+ ' return False\n'
+ '\n'
+ '\n'
+ 'async def stream():\n'
+ ' for slug in ["json", "datetime"]:\n'
+ ' await asyncio.sleep(0)\n'
+ ' yield slug\n'
+ '\n'
+ '\n'
+ 'async def driver():\n'
+ ' async with Session():\n'
+ ' async for slug in stream():\n'
+ ' print(slug)\n'
+ '\n'
+ 'asyncio.run(driver())',
+ 'prose': '`async with` and `async for` are the asynchronous forms of context '
+ 'managers and iteration. A class implements `__aenter__`/`__aexit__` '
+ 'to act as an async context manager; an `async def` function with '
+ '`yield` becomes an async generator. The dedicated [async iteration '
+ 'and context](/iteration/async-iteration-and-context) page explains '
+ 'the protocols in depth.'}]},
+ {'cells': [{'code': 'import asyncio\n'
'\n'
'async def titles():\n'
' for slug in ["values", "async-await"]:\n'
@@ -4865,12 +10640,12 @@
' yield slug.replace("-", " ").title()\n'
'\n'
'print(titles.__name__)',
- 'output': 'titles',
+ 'kind': 'cell',
'line': 24,
- 'kind': 'cell'},
- {'prose': ['An async context manager defines `__aenter__` and `__aexit__`. `async with` awaits setup and '
- 'cleanup around the block.'],
- 'code': 'class Session:\n'
+ 'output': 'titles',
+ 'prose': ['An async generator can `await` before yielding each value. `async for` '
+ 'consumes those values with the asynchronous iteration protocol.']},
+ {'code': 'class Session:\n'
' async def __aenter__(self):\n'
' print("open")\n'
' return self\n'
@@ -4879,17 +10654,92 @@
' print("close")\n'
'\n'
'print(Session.__name__)',
- 'output': 'Session',
+ 'kind': 'cell',
'line': 43,
- 'kind': 'cell'},
- {'prose': ['The top-level coroutine combines both protocols: open the async resource, then consume the '
- 'async stream inside it.'],
- 'code': 'async def main():\n'
+ 'output': 'Session',
+ 'prose': ['An async context manager defines `__aenter__` and `__aexit__`. `async '
+ 'with` awaits setup and cleanup around the block.']},
+ {'code': 'async def main():\n'
' async with Session():\n'
' async for title in titles():\n'
' print(title)\n'
'\n'
'asyncio.run(main())',
- 'output': 'open\nValues\nAsync Await\nclose',
+ 'kind': 'cell',
'line': 63,
- 'kind': 'cell'}]}]
+ 'output': 'open\nValues\nAsync Await\nclose',
+ 'prose': ['The top-level coroutine combines both protocols: open the async resource, '
+ 'then consume the async stream inside it.']}],
+ 'code': 'import asyncio\n'
+ '\n'
+ 'async def titles():\n'
+ ' for slug in ["values", "async-await"]:\n'
+ ' await asyncio.sleep(0)\n'
+ ' yield slug.replace("-", " ").title()\n'
+ '\n'
+ 'class Session:\n'
+ ' async def __aenter__(self):\n'
+ ' print("open")\n'
+ ' return self\n'
+ '\n'
+ ' async def __aexit__(self, exc_type, exc, tb):\n'
+ ' print("close")\n'
+ '\n'
+ 'async def main():\n'
+ ' async with Session():\n'
+ ' async for title in titles():\n'
+ ' print(title)\n'
+ '\n'
+ 'asyncio.run(main())\n',
+ 'doc_path': '/reference/compound_stmts.html#async-for',
+ 'doc_url': 'https://docs.python.org/3.13/reference/compound_stmts.html#async-for',
+ 'expected_output': 'open\nValues\nAsync Await\nclose\n',
+ 'explanation': ['`async for` consumes an asynchronous iterator: a stream whose next value may '
+ 'require `await`. `async with` surrounds a block with asynchronous setup and '
+ 'cleanup.',
+ 'These forms appear around network streams, database cursors, locks, and service '
+ 'clients where both iteration and cleanup may wait on I/O.',
+ 'Use ordinary `for` and `with` when producing the next value or cleaning up does '
+ 'not need to await anything.',
+ 'The syntax mirrors `for` and `with`, but the protocol methods are '
+ 'asynchronous.'],
+ 'min_python': None,
+ 'notes': ['`async for` consumes asynchronous iterators.',
+ '`async with` awaits asynchronous setup and cleanup.',
+ 'These forms are common around I/O-shaped resources.'],
+ 'section': 'Async',
+ 'see_also': ['async-await', 'iterators', 'context-managers'],
+ 'slug': 'async-iteration-and-context',
+ 'summary': 'async for and async with consume asynchronous streams and cleanup protocols.',
+ 'title': 'Async Iteration and Context',
+ 'version_notes': None,
+ 'version_sensitive': False,
+ 'walkthrough': [{'code': 'import asyncio\n'
+ '\n'
+ 'async def titles():\n'
+ ' for slug in ["values", "async-await"]:\n'
+ ' await asyncio.sleep(0)\n'
+ ' yield slug.replace("-", " ").title()\n'
+ '\n'
+ 'print(titles.__name__)',
+ 'prose': 'An async generator can `await` before yielding each value. `async '
+ 'for` consumes those values with the asynchronous iteration protocol.'},
+ {'code': 'class Session:\n'
+ ' async def __aenter__(self):\n'
+ ' print("open")\n'
+ ' return self\n'
+ '\n'
+ ' async def __aexit__(self, exc_type, exc, tb):\n'
+ ' print("close")\n'
+ '\n'
+ 'print(Session.__name__)',
+ 'prose': 'An async context manager defines `__aenter__` and `__aexit__`. `async '
+ 'with` awaits setup and cleanup around the block.'},
+ {'code': 'async def main():\n'
+ ' async with Session():\n'
+ ' async for title in titles():\n'
+ ' print(title)\n'
+ '\n'
+ 'asyncio.run(main())',
+ 'prose': 'The top-level coroutine combines both protocols: open the async '
+ 'resource, then consume the async stream inside it.'}]}]
diff --git a/tests/test_quality_checks.py b/tests/test_quality_checks.py
new file mode 100644
index 0000000..1075a1d
--- /dev/null
+++ b/tests/test_quality_checks.py
@@ -0,0 +1,148 @@
+"""Tests for the four quality-check scripts.
+
+Each test runs the script as a subprocess against a temporary registry
+or example directory so the script's command-line behavior is what is
+validated, not just its internals.
+"""
+import subprocess
+import sys
+import tempfile
+import textwrap
+import unittest
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[1]
+SCRIPTS = ROOT / "scripts"
+
+
+def run(script: str, *, env_overrides: dict[str, str] | None = None) -> subprocess.CompletedProcess:
+ return subprocess.run(
+ [sys.executable, str(SCRIPTS / script)],
+ capture_output=True,
+ text=True,
+ cwd=ROOT,
+ )
+
+
+class CheckScriptSmokeTests(unittest.TestCase):
+ """The shipped catalog must pass every gate."""
+
+ def test_registry_integrity_passes(self):
+ result = run("check_registry_integrity.py")
+ self.assertEqual(result.returncode, 0, msg=result.stderr)
+ self.assertIn("Registry integrity OK", result.stdout)
+
+ def test_confusable_pairs_pass(self):
+ result = run("check_confusable_pairs.py")
+ self.assertEqual(result.returncode, 0, msg=result.stderr)
+ self.assertIn("Confusable-pair coverage OK", result.stdout)
+
+ def test_broad_surface_tours_pass(self):
+ result = run("check_broad_surface_tours.py")
+ self.assertEqual(result.returncode, 0, msg=result.stderr)
+ self.assertIn("Broad-surface tour coverage OK", result.stdout)
+
+ def test_footgun_coverage_passes(self):
+ result = run("check_footgun_coverage.py")
+ self.assertEqual(result.returncode, 0, msg=result.stderr)
+ self.assertIn("Footgun coverage OK", result.stdout)
+
+ def test_notes_supported_passes(self):
+ result = run("check_notes_supported.py")
+ self.assertEqual(result.returncode, 0, msg=result.stderr)
+ self.assertIn("Notes-supported check OK", result.stdout)
+
+
+class RegistryIntegrityTests(unittest.TestCase):
+ """The integrity script catches typos before they reach the coverage checks."""
+
+ def _write_and_run(self, registry_text: str) -> subprocess.CompletedProcess:
+ # Patch the script in-memory by overriding REGISTRY_PATH via a
+ # generated wrapper. Simpler: write the registry to a temp file and
+ # patch the script's module-level constant by invoking it through a
+ # small Python program.
+ with tempfile.NamedTemporaryFile("w", suffix=".toml", delete=False) as fh:
+ fh.write(registry_text)
+ registry_path = Path(fh.name)
+ program = textwrap.dedent(f"""
+ import sys
+ from pathlib import Path
+ sys.path.insert(0, {str(SCRIPTS)!r})
+ import check_registry_integrity as mod
+ mod.REGISTRY_PATH = Path({str(registry_path)!r})
+ sys.exit(mod.main())
+ """)
+ return subprocess.run(
+ [sys.executable, "-c", program],
+ capture_output=True,
+ text=True,
+ cwd=ROOT,
+ )
+
+ def test_unknown_owner_in_confusable_pair_fails(self):
+ result = self._write_and_run(
+ textwrap.dedent("""
+ [[confusable_pairs]]
+ name = "fake pair"
+ owner = "no-such-page"
+ tokens = ["a", "b"]
+ """)
+ )
+ self.assertEqual(result.returncode, 1)
+ self.assertIn("unknown owner", result.stderr)
+ self.assertIn("no-such-page", result.stderr)
+
+ def test_missing_tokens_in_confusable_pair_fails(self):
+ result = self._write_and_run(
+ textwrap.dedent("""
+ [[confusable_pairs]]
+ name = "empty"
+ owner = "hello-world"
+ tokens = []
+ """)
+ )
+ self.assertEqual(result.returncode, 1)
+ self.assertIn("no tokens", result.stderr)
+
+ def test_unknown_footgun_owner_fails(self):
+ result = self._write_and_run(
+ textwrap.dedent("""
+ [[footguns]]
+ name = "fake"
+ owner = "no-such-page"
+ broken_tokens = ["x"]
+ fixed_tokens = ["y"]
+ """)
+ )
+ self.assertEqual(result.returncode, 1)
+ self.assertIn("unknown owner", result.stderr)
+
+ def test_unknown_broad_tour_slug_fails(self):
+ result = self._write_and_run(
+ textwrap.dedent("""
+ [broad_surface_tours.no-such-page]
+ required_tokens = ["x"]
+ """)
+ )
+ self.assertEqual(result.returncode, 1)
+ self.assertIn("unknown slug", result.stderr)
+
+
+class NotesSupportedHeuristicTests(unittest.TestCase):
+ """The notes check should accept grounded bullets and reject ungrounded ones."""
+
+ def test_token_extraction_drops_stopwords(self):
+ sys.path.insert(0, str(SCRIPTS))
+ try:
+ import check_notes_supported as mod
+ finally:
+ sys.path.pop(0)
+ extracted = mod.tokens("The quick fox uses functions and methods.")
+ self.assertNotIn("the", extracted)
+ self.assertNotIn("functions", extracted) # filtered
+ self.assertIn("quick", extracted)
+ self.assertIn("fox", extracted)
+
+
+if __name__ == "__main__":
+ unittest.main()