From 5a07fad36805fbd27328d3c443168d962fb86fc1 Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Wed, 23 Sep 2026 22:04:25 -0700 Subject: [PATCH 1/7] mega create parent nav sections --- STRUCTURE.md | 2 +- docs/{ => flow}/conditionals.md | 0 docs/{ => flow}/loops.md | 4 +- docs/index.md | 786 +++++++++++++-------------- docs/libraries/beautifulsoup.md | 4 +- docs/libraries/collections.md | 4 +- docs/libraries/numpy.md | 4 +- docs/libraries/pillow.md | 8 +- docs/libraries/pytest.md | 2 +- docs/libraries/re.md | 2 +- docs/libraries/requests.md | 4 +- docs/libraries/turtle.md | 6 +- docs/{ => organization}/classes.md | 8 +- docs/{ => organization}/functions.md | 20 +- docs/{ => practices}/errors.md | 20 +- docs/{ => practices}/style.md | 58 +- docs/{ => resources}/files.md | 16 +- docs/{ => resources}/modules.md | 4 +- docs/{ => start}/foundations.md | 36 +- docs/{ => start}/workspace.md | 6 +- docs/{types.md => types/basics.md} | 6 +- docs/{ => types}/collections.md | 52 +- mkdocs.yml | 30 +- tests/test_accessibility_browser.py | 10 +- tests/test_accessibility_keyboard.py | 10 +- tests/test_accessibility_runnable.py | 2 +- tests/test_essentials_toggle.py | 16 +- tests/test_structure.py | 2 +- 28 files changed, 566 insertions(+), 556 deletions(-) rename docs/{ => flow}/conditionals.md (100%) rename docs/{ => flow}/loops.md (98%) rename docs/{ => organization}/classes.md (96%) rename docs/{ => organization}/functions.md (94%) rename docs/{ => practices}/errors.md (89%) rename docs/{ => practices}/style.md (85%) rename docs/{ => resources}/files.md (91%) rename docs/{ => resources}/modules.md (96%) rename docs/{ => start}/foundations.md (90%) rename docs/{ => start}/workspace.md (96%) rename docs/{types.md => types/basics.md} (98%) rename docs/{ => types}/collections.md (93%) diff --git a/STRUCTURE.md b/STRUCTURE.md index a30d898..816942a 100644 --- a/STRUCTURE.md +++ b/STRUCTURE.md @@ -249,7 +249,7 @@ Pick the existing type that matches the branch, don't invent new ones without a | `??? info` | Defining a term/concept adjacent to the page but not the topic itself. | | `??? failure` | The negative counterpart to a `success` branch — "this didn't work, here's what to do about it" (e.g. workspace.md's "download Python here" branch when `python --version` doesn't show 3.x.x). | | `??? ai` | Opinion/meta content specifically about learning with or around AI (e.g. index.md's FAQ tabs on whether/how to use AI while learning) — not used for teaching content about Python itself. | -| `??? efficiency` | A runtime/space aside naming the cost behind a choice already shown in prose (e.g. list vs. set membership, `sort()` vs. `sorted()`) — usually a Big O difference, occasionally a constant-factor one (`.get()` vs. two hash lookups, vectorized NumPy vs. a Python loop) where it's still worth flagging but doesn't change the O(...) class. Wrap it in `
` on a page that participates in the Essentials/Advanced toggle (skip it on a page that doesn't, like the library reference pages), and close with a link to [style.md's "Efficiency"](docs/style.md#efficiency) section. Formalizes a tradeoff the surrounding prose already states in plain language; doesn't introduce the tradeoff for the first time. | +| `??? efficiency` | A runtime/space aside naming the cost behind a choice already shown in prose (e.g. list vs. set membership, `sort()` vs. `sorted()`) — usually a Big O difference, occasionally a constant-factor one (`.get()` vs. two hash lookups, vectorized NumPy vs. a Python loop) where it's still worth flagging but doesn't change the O(...) class. Wrap it in `
` on a page that participates in the Essentials/Advanced toggle (skip it on a page that doesn't, like the library reference pages), and close with a link to [style.md's "Efficiency"](docs/practices/style.md#efficiency) section. Formalizes a tradeoff the surrounding prose already states in plain language; doesn't introduce the tradeoff for the first time. | | `!!! example` | An always-open side-by-side comparison the reader is meant to see without a click, not a branch — e.g. "how to loop each type," showing every collection type's loop pattern in one visible table. | Default to collapsed (`???`), not always-open (`!!!`) — an always-open admonition competes with diff --git a/docs/conditionals.md b/docs/flow/conditionals.md similarity index 100% rename from docs/conditionals.md rename to docs/flow/conditionals.md diff --git a/docs/loops.md b/docs/flow/loops.md similarity index 98% rename from docs/loops.md rename to docs/flow/loops.md index 43f8eda..82ae87e 100644 --- a/docs/loops.md +++ b/docs/flow/loops.md @@ -204,7 +204,7 @@ Naming the variable in a `range()` loop comes down to one of three choices: - **A descriptive name, when the count means something** - If what you're counting through actually represents something, a descriptive name reads better than `i` — says what the number *means* at a glance, instead of leaving the reader to infer it from how it's used. Same [naming](style.md#naming) rule as any other variable: `i` is fine for a short, throwaway loop, but a meaningful name is worth it once the number stands for something specific. + If what you're counting through actually represents something, a descriptive name reads better than `i` — says what the number *means* at a glance, instead of leaving the reader to infer it from how it's used. Same [naming](../practices/style.md#naming) rule as any other variable: `i` is fine for a short, throwaway loop, but a meaningful name is worth it once the number stands for something specific. ```python for year in range(2020, 2026): @@ -229,7 +229,7 @@ Naming the variable in a `range()` loop comes down to one of three choices: A `for` loop steps through any type of collection[^str-collection] the same way — the difference is what each pass hands you to work with. -[^str-collection]: A string isn't technically one of Python's collection types — see the [Types](types.md#strings) page — but it's structurally iterable and indexable the same way a list is, so it loops the same way too. +[^str-collection]: A string isn't technically one of Python's collection types — see the [Types](../types/basics.md#strings) page — but it's structurally iterable and indexable the same way a list is, so it loops the same way too. !!! example "How to loop each type" diff --git a/docs/index.md b/docs/index.md index 3ea4a2e..5dcad12 100644 --- a/docs/index.md +++ b/docs/index.md @@ -89,61 +89,61 @@ hide:
-- :material-monitor:{ .lg .middle } [__Workspace Setup__](workspace.md) +- :material-monitor:{ .lg .middle } [__Workspace Setup__](start/workspace.md) Write Python on your computer. - [**`install`**](workspace.md#step-0-install-python): - [`download`](workspace.md#step-0-install-python) - [`version`](workspace.md#step-0-install-python) + [**`install`**](start/workspace.md#step-0-install-python): + [`download`](start/workspace.md#step-0-install-python) + [`version`](start/workspace.md#step-0-install-python) - [**`code editors`**](workspace.md#step-1-pick-an-application-to-write-code-in): - [`IDLE`](workspace.md#step-1-pick-an-application-to-write-code-in) - [`Pycharm`](workspace.md#step-1-pick-an-application-to-write-code-in) - [`Thonny`](workspace.md#step-1-pick-an-application-to-write-code-in) - [`VS Code`](workspace.md#step-1-pick-an-application-to-write-code-in) + [**`code editors`**](start/workspace.md#step-1-pick-an-application-to-write-code-in): + [`IDLE`](start/workspace.md#step-1-pick-an-application-to-write-code-in) + [`Pycharm`](start/workspace.md#step-1-pick-an-application-to-write-code-in) + [`Thonny`](start/workspace.md#step-1-pick-an-application-to-write-code-in) + [`VS Code`](start/workspace.md#step-1-pick-an-application-to-write-code-in) - [**`how to write and run .py file`**](workspace.md#step-2-write-and-run-a-python-file): - [`file naming`](workspace.md#step-2-write-and-run-a-python-file) + [**`how to write and run .py file`**](start/workspace.md#step-2-write-and-run-a-python-file): + [`file naming`](start/workspace.md#step-2-write-and-run-a-python-file) - [**`Terminal`**](workspace.md#using-the-terminal): - [`cd`](workspace.md#using-the-terminal) - [`ls`](workspace.md#using-the-terminal) - [`pwd`](workspace.md#using-the-terminal) - [`shortcuts`](workspace.md#using-the-terminal) + [**`Terminal`**](start/workspace.md#using-the-terminal): + [`cd`](start/workspace.md#using-the-terminal) + [`ls`](start/workspace.md#using-the-terminal) + [`pwd`](start/workspace.md#using-the-terminal) + [`shortcuts`](start/workspace.md#using-the-terminal) {: data-advanced="true" } - [**`virtual environments`**](workspace.md#virtual-environments): - [`activate`](workspace.md#virtual-environments) - [`pip`](workspace.md#virtual-environments) - [`requirements.txt`](workspace.md#virtual-environments) - [`venv`](workspace.md#virtual-environments) + [**`virtual environments`**](start/workspace.md#virtual-environments): + [`activate`](start/workspace.md#virtual-environments) + [`pip`](start/workspace.md#virtual-environments) + [`requirements.txt`](start/workspace.md#virtual-environments) + [`venv`](start/workspace.md#virtual-environments) {: data-advanced="true" } -- :material-cube-outline:{ .lg .middle } [__Foundations__](foundations.md) +- :material-cube-outline:{ .lg .middle } [__Foundations__](start/foundations.md) Storing, displaying, and inputting values. - [**`variables`**](foundations.md#variables): - [`naming`](foundations.md#naming-variables) - [`printing`](foundations.md#printing-variables) - [`reassigning`](foundations.md#reassigning-a-variable) - [`types`](foundations.md#variables-and-types) + [**`variables`**](start/foundations.md#variables): + [`naming`](start/foundations.md#naming-variables) + [`printing`](start/foundations.md#printing-variables) + [`reassigning`](start/foundations.md#reassigning-a-variable) + [`types`](start/foundations.md#variables-and-types) - [**`expressions and statements`**](foundations.md#expressions-and-statements) + [**`expressions and statements`**](start/foundations.md#expressions-and-statements) - [**`print`**](foundations.md#print-function): - [`escape sequences`](foundations.md#escape-sequences) + [**`print`**](start/foundations.md#print-function): + [`escape sequences`](start/foundations.md#escape-sequences) - [**`input`**](foundations.md#input-function) + [**`input`**](start/foundations.md#input-function) - [**`comments`**](foundations.md#comments): - [`"""`](foundations.md#multi-line-comments-with) - [`#`](foundations.md#single-line-comments-with) - [`FIXME`](foundations.md#single-line-comments-with) - [`TODO`](foundations.md#single-line-comments-with) + [**`comments`**](start/foundations.md#comments): + [`"""`](start/foundations.md#multi-line-comments-with) + [`#`](start/foundations.md#single-line-comments-with) + [`FIXME`](start/foundations.md#single-line-comments-with) + [`TODO`](start/foundations.md#single-line-comments-with) - [**`tips for getting started`**](foundations.md#tips-for-getting-started) + [**`tips for getting started`**](start/foundations.md#tips-for-getting-started)
@@ -153,161 +153,161 @@ hide:
-- :material-shape-outline:{ .lg .middle } [__Basics__](types.md) +- :material-shape-outline:{ .lg .middle } [__Basics__](types/basics.md) Kinds of values, and what you can do with them. - [`isinstance`](types.md) - [`type`](types.md) - - [**`integers`**](types.md#integers): - [`+ - * / **`](types.md#arithmetic) - [`+= -= *= /= //= %= **=`](types.md#apply-arithmetic-to-a-variable) - [`// % divmod`](types.md#floor-division-modulo) - [`abs`](types.md#absolute-value) - [`boolean expressions`](types.md#boolean-expressions) - [`int`](types.md#convert) - - [**`floats`**](types.md#floats): - [`+ - * / **`](types.md#arithmetic_1) - [`+= -= *= /= //= %= **=`](types.md#apply-arithmetic-to-a-variable_1) - [`// % divmod`](types.md#floor-division-modulo_1) - [`abs`](types.md#adjust) - [`boolean expressions`](types.md#boolean-expressions_1) - [`float`](types.md#convert_1) - [`round`](types.md#adjust) - - [**`strings`**](types.md#strings): - [`+ * += *=`](types.md#combine) - [`boolean expressions`](types.md#boolean-expressions_2) - [`capitalize`](types.md#modify) - [`combine`](types.md#combine) - [`count`](types.md#search) - [`endswith`](types.md#validate) - [`f-string`](types.md#building-strings) - [`find`](types.md#search) - [`format`](types.md#building-strings) - [`format spec`](types.md#building-strings) - [`in`](types.md#search) - [`index`](types.md#access-characters) - [`isalpha`](types.md#validate) - [`isdigit`](types.md#validate) - [`join`](types.md#combine) - [`len`](types.md#inspect) - [`lower`](types.md#modify) - [`replace`](types.md#modify) - [`slice`](types.md#access-characters) - [`split`](types.md#convert_2) - [`startswith`](types.md#validate) - [`step`](types.md#access-characters) - [`str`](types.md#convert_2) - [`strip`](types.md#modify) - [`title`](types.md#modify) - [`upper`](types.md#modify) - - [**`booleans`**](types.md#booleans): - [`== != > < >= <=`](types.md#boolean-expressions_3) - [`and`](types.md#logical-operators) - [`in`](types.md#boolean-expressions_3) - [`is`](types.md#boolean-expressions_3) - [`not`](types.md#logical-operators) - [`or`](types.md#logical-operators) - - [**`None`**](types.md#none): - [`boolean expressions`](types.md#boolean-expressions_4) - [`is`](types.md#check-for-none) - [`is not`](types.md#check-for-none) - -- :material-basket-outline:{ .lg .middle } [__Collections__](collections.md) + [`isinstance`](types/basics.md) + [`type`](types/basics.md) + + [**`integers`**](types/basics.md#integers): + [`+ - * / **`](types/basics.md#arithmetic) + [`+= -= *= /= //= %= **=`](types/basics.md#apply-arithmetic-to-a-variable) + [`// % divmod`](types/basics.md#floor-division-modulo) + [`abs`](types/basics.md#absolute-value) + [`boolean expressions`](types/basics.md#boolean-expressions) + [`int`](types/basics.md#convert) + + [**`floats`**](types/basics.md#floats): + [`+ - * / **`](types/basics.md#arithmetic_1) + [`+= -= *= /= //= %= **=`](types/basics.md#apply-arithmetic-to-a-variable_1) + [`// % divmod`](types/basics.md#floor-division-modulo_1) + [`abs`](types/basics.md#adjust) + [`boolean expressions`](types/basics.md#boolean-expressions_1) + [`float`](types/basics.md#convert_1) + [`round`](types/basics.md#adjust) + + [**`strings`**](types/basics.md#strings): + [`+ * += *=`](types/basics.md#combine) + [`boolean expressions`](types/basics.md#boolean-expressions_2) + [`capitalize`](types/basics.md#modify) + [`combine`](types/basics.md#combine) + [`count`](types/basics.md#search) + [`endswith`](types/basics.md#validate) + [`f-string`](types/basics.md#building-strings) + [`find`](types/basics.md#search) + [`format`](types/basics.md#building-strings) + [`format spec`](types/basics.md#building-strings) + [`in`](types/basics.md#search) + [`index`](types/basics.md#access-characters) + [`isalpha`](types/basics.md#validate) + [`isdigit`](types/basics.md#validate) + [`join`](types/basics.md#combine) + [`len`](types/basics.md#inspect) + [`lower`](types/basics.md#modify) + [`replace`](types/basics.md#modify) + [`slice`](types/basics.md#access-characters) + [`split`](types/basics.md#convert_2) + [`startswith`](types/basics.md#validate) + [`step`](types/basics.md#access-characters) + [`str`](types/basics.md#convert_2) + [`strip`](types/basics.md#modify) + [`title`](types/basics.md#modify) + [`upper`](types/basics.md#modify) + + [**`booleans`**](types/basics.md#booleans): + [`== != > < >= <=`](types/basics.md#boolean-expressions_3) + [`and`](types/basics.md#logical-operators) + [`in`](types/basics.md#boolean-expressions_3) + [`is`](types/basics.md#boolean-expressions_3) + [`not`](types/basics.md#logical-operators) + [`or`](types/basics.md#logical-operators) + + [**`None`**](types/basics.md#none): + [`boolean expressions`](types/basics.md#boolean-expressions_4) + [`is`](types/basics.md#check-for-none) + [`is not`](types/basics.md#check-for-none) + +- :material-basket-outline:{ .lg .middle } [__Collections__](types/collections.md) Multiple related values grouped into one container. - [`isinstance`](collections.md) - [`type`](collections.md) - - [**`lists`**](collections.md#lists): - [`+`](collections.md#create) - [`append`](collections.md#add-item) - [`boolean expressions`](collections.md#boolean-expressions) - [`clear`](collections.md#remove-item) - [`comprehension`](collections.md#list-comprehension) - [`copy`](collections.md#create) - [`count`](collections.md#inspect) - [`create`](collections.md#create-a-list) - [`del`](collections.md#remove-item) - [`extend`](collections.md#add-item) - [`in`](collections.md#boolean-expressions) - [`index`](collections.md#create-a-list) - [`insert`](collections.md#add-item) - [`item`](collections.md#lists) - [`len`](collections.md#inspect) - [`list`](collections.md#create) - [`loop`](collections.md#loop-through-a-list) - [`max`](collections.md#arithmetic) - [`min`](collections.md#arithmetic) - [`pop`](collections.md#remove-item) - [`remove`](collections.md#remove-item) - [`reverse`](collections.md#sort) - [`slice`](collections.md#access-and-update-items) - [`sort`](collections.md#sort) - [`sorted`](collections.md#sort) - [`step`](collections.md#access-and-update-items) - [`sum`](collections.md#arithmetic) - - [**`dictionaries`**](collections.md#dictionaries): - [`access a value`](collections.md#access-a-value) - [`boolean expressions`](collections.md#boolean-expressions_1) - [`clear`](collections.md#remove_1) - [`copy`](collections.md#create_1) - [`del`](collections.md#remove_1) - [`dict`](collections.md#create_1) - [`get`](collections.md#dictionary-operations) - [`items`](collections.md#loop-through-a-dictionary) - [`key`](collections.md#dictionaries) - [`len`](collections.md#inspect_1) - [`loop`](collections.md#loop-through-a-dictionary) - [`pop`](collections.md#remove_1) - [`popitem`](collections.md#remove_1) - [`update`](collections.md#update_1) - [`value`](collections.md#dictionaries) - [`values`](collections.md#loop-through-a-dictionary) - - [**`tuples`**](collections.md#tuples): - [`access items`](collections.md#access-items) - [`boolean expressions`](collections.md#boolean-expressions_2) - [`count`](collections.md#inspect_2) - [`immmutable`](collections.md#tuples) - [`index`](collections.md#tuples) - [`index`](collections.md#inspect_2) - [`len`](collections.md#inspect_2) - [`loop`](collections.md#loop-through-a-tuple) - [`max`](collections.md#arithmetic_1) - [`min`](collections.md#arithmetic_1) - [`packing`](collections.md#packing-and-unpacking) - [`sum`](collections.md#arithmetic_1) - [`tuple`](collections.md#create_2) - [`unpacking`](collections.md#packing-and-unpacking) + [`isinstance`](types/collections.md) + [`type`](types/collections.md) + + [**`lists`**](types/collections.md#lists): + [`+`](types/collections.md#create) + [`append`](types/collections.md#add-item) + [`boolean expressions`](types/collections.md#boolean-expressions) + [`clear`](types/collections.md#remove-item) + [`comprehension`](types/collections.md#list-comprehension) + [`copy`](types/collections.md#create) + [`count`](types/collections.md#inspect) + [`create`](types/collections.md#create-a-list) + [`del`](types/collections.md#remove-item) + [`extend`](types/collections.md#add-item) + [`in`](types/collections.md#boolean-expressions) + [`index`](types/collections.md#create-a-list) + [`insert`](types/collections.md#add-item) + [`item`](types/collections.md#lists) + [`len`](types/collections.md#inspect) + [`list`](types/collections.md#create) + [`loop`](types/collections.md#loop-through-a-list) + [`max`](types/collections.md#arithmetic) + [`min`](types/collections.md#arithmetic) + [`pop`](types/collections.md#remove-item) + [`remove`](types/collections.md#remove-item) + [`reverse`](types/collections.md#sort) + [`slice`](types/collections.md#access-and-update-items) + [`sort`](types/collections.md#sort) + [`sorted`](types/collections.md#sort) + [`step`](types/collections.md#access-and-update-items) + [`sum`](types/collections.md#arithmetic) + + [**`dictionaries`**](types/collections.md#dictionaries): + [`access a value`](types/collections.md#access-a-value) + [`boolean expressions`](types/collections.md#boolean-expressions_1) + [`clear`](types/collections.md#remove_1) + [`copy`](types/collections.md#create_1) + [`del`](types/collections.md#remove_1) + [`dict`](types/collections.md#create_1) + [`get`](types/collections.md#dictionary-operations) + [`items`](types/collections.md#loop-through-a-dictionary) + [`key`](types/collections.md#dictionaries) + [`len`](types/collections.md#inspect_1) + [`loop`](types/collections.md#loop-through-a-dictionary) + [`pop`](types/collections.md#remove_1) + [`popitem`](types/collections.md#remove_1) + [`update`](types/collections.md#update_1) + [`value`](types/collections.md#dictionaries) + [`values`](types/collections.md#loop-through-a-dictionary) + + [**`tuples`**](types/collections.md#tuples): + [`access items`](types/collections.md#access-items) + [`boolean expressions`](types/collections.md#boolean-expressions_2) + [`count`](types/collections.md#inspect_2) + [`immmutable`](types/collections.md#tuples) + [`index`](types/collections.md#tuples) + [`index`](types/collections.md#inspect_2) + [`len`](types/collections.md#inspect_2) + [`loop`](types/collections.md#loop-through-a-tuple) + [`max`](types/collections.md#arithmetic_1) + [`min`](types/collections.md#arithmetic_1) + [`packing`](types/collections.md#packing-and-unpacking) + [`sum`](types/collections.md#arithmetic_1) + [`tuple`](types/collections.md#create_2) + [`unpacking`](types/collections.md#packing-and-unpacking) {: data-advanced="true" } - [**`sets`**](collections.md#sets): - [`add`](collections.md#update_1) - [`boolean expressions`](collections.md#boolean-expressions_3) - [`clear`](collections.md#remove_1) - [`copy`](collections.md#create_3) - [`discard`](collections.md#remove_1) - [`isdisjoint`](collections.md#compare) - [`issubset`](collections.md#compare) - [`issuperset`](collections.md#compare) - [`len`](collections.md#inspect_3) - [`loop`](collections.md#loop-through-a-set) - [`max`](collections.md#arithmetic_2) - [`min`](collections.md#arithmetic_2) - [`pop`](collections.md#remove_1) - [`remove`](collections.md#remove_1) - [`set`](collections.md#create_3) - [`sum`](collections.md#arithmetic_2) - [`update`](collections.md#update_1) - [`| & - ^`](collections.md#combine) + [**`sets`**](types/collections.md#sets): + [`add`](types/collections.md#update_1) + [`boolean expressions`](types/collections.md#boolean-expressions_3) + [`clear`](types/collections.md#remove_1) + [`copy`](types/collections.md#create_3) + [`discard`](types/collections.md#remove_1) + [`isdisjoint`](types/collections.md#compare) + [`issubset`](types/collections.md#compare) + [`issuperset`](types/collections.md#compare) + [`len`](types/collections.md#inspect_3) + [`loop`](types/collections.md#loop-through-a-set) + [`max`](types/collections.md#arithmetic_2) + [`min`](types/collections.md#arithmetic_2) + [`pop`](types/collections.md#remove_1) + [`remove`](types/collections.md#remove_1) + [`set`](types/collections.md#create_3) + [`sum`](types/collections.md#arithmetic_2) + [`update`](types/collections.md#update_1) + [`| & - ^`](types/collections.md#combine) {: data-advanced="true" }
@@ -318,158 +318,158 @@ hide:
-- :material-source-branch:{ .lg .middle } [__Conditionals__](conditionals.md) +- :material-source-branch:{ .lg .middle } [__Conditionals__](flow/conditionals.md) Decision points that run code only if a condition is met. - [**`if, elif, else`**](conditionals.md#if-elif-else): - [`and, or, not`](conditionals.md#logical-operators) - [`boolean expressions`](conditionals.md#boolean-expressions) + [**`if, elif, else`**](flow/conditionals.md#if-elif-else): + [`and, or, not`](flow/conditionals.md#logical-operators) + [`boolean expressions`](flow/conditionals.md#boolean-expressions) - [**`match, case`**](conditionals.md#match-case): - [`_ wildcard`](conditionals.md#default-value-_) - [`case + if`](conditionals.md#case-if) - [`match with |`](conditionals.md#match-multiple-values-with) - [`unpacking`](conditionals.md#unpacking-a-tuple) + [**`match, case`**](flow/conditionals.md#match-case): + [`_ wildcard`](flow/conditionals.md#default-value-_) + [`case + if`](flow/conditionals.md#case-if) + [`match with |`](flow/conditionals.md#match-multiple-values-with) + [`unpacking`](flow/conditionals.md#unpacking-a-tuple) - [**`control flow`**](conditionals.md#control-flow-statements): - [`break`](conditionals.md#break) - [`continue`](conditionals.md#continue) - [`pass`](conditionals.md#going-further_2) + [**`control flow`**](flow/conditionals.md#control-flow-statements): + [`break`](flow/conditionals.md#break) + [`continue`](flow/conditionals.md#continue) + [`pass`](flow/conditionals.md#going-further_2) -- :material-repeat:{ .lg .middle } [__Loops__](loops.md) +- :material-repeat:{ .lg .middle } [__Loops__](flow/loops.md) Repeat a block of code multiple times. - [**`for`**](loops.md#for-loops): - [`enumerate`](loops.md#loop-with-index-and-value) - [`loop a set number of times`](loops.md#loop-a-certain-number-of-times) - [`loop through a collection`](loops.md#loop-through-a-collection) - [`range`](loops.md#iterable-range) - [`reversed`](loops.md#loop-in-reverse) - [`zip`](loops.md#loop-with-index-and-value) - - [**`while`**](loops.md#while-loops): - [`and`](loops.md#logical-operators) - [`boolean expressions`](loops.md#boolean-expressions) - [`counter and flag names`](loops.md#counter-and-flag-names) - [`flag`](loops.md#using-a-flag) - [`not`](loops.md#logical-operators) - [`or`](loops.md#logical-operators) - [`sentinel`](loops.md#sentinel) - - [**`common patterns`**](loops.md#common-patterns): - [`accumulator`](loops.md#accumulator) - [`counter`](loops.md#counter) - [`nested loops`](loops.md#nested-loops) - - [**`control flow`**](loops.md#control-flow-statements): - [`break`](loops.md#break) - [`continue`](loops.md#continue) - [`else`](loops.md#else) - [`pass`](loops.md#going-further_2) + [**`for`**](flow/loops.md#for-loops): + [`enumerate`](flow/loops.md#loop-with-index-and-value) + [`loop a set number of times`](flow/loops.md#loop-a-certain-number-of-times) + [`loop through a collection`](flow/loops.md#loop-through-a-collection) + [`range`](flow/loops.md#iterable-range) + [`reversed`](flow/loops.md#loop-in-reverse) + [`zip`](flow/loops.md#loop-with-index-and-value) + + [**`while`**](flow/loops.md#while-loops): + [`and`](flow/loops.md#logical-operators) + [`boolean expressions`](flow/loops.md#boolean-expressions) + [`counter and flag names`](flow/loops.md#counter-and-flag-names) + [`flag`](flow/loops.md#using-a-flag) + [`not`](flow/loops.md#logical-operators) + [`or`](flow/loops.md#logical-operators) + [`sentinel`](flow/loops.md#sentinel) + + [**`common patterns`**](flow/loops.md#common-patterns): + [`accumulator`](flow/loops.md#accumulator) + [`counter`](flow/loops.md#counter) + [`nested loops`](flow/loops.md#nested-loops) + + [**`control flow`**](flow/loops.md#control-flow-statements): + [`break`](flow/loops.md#break) + [`continue`](flow/loops.md#continue) + [`else`](flow/loops.md#else) + [`pass`](flow/loops.md#going-further_2)
-#### Code organization { .pt-homepage-heading } +#### Organization { .pt-homepage-heading }
-- :material-function-variant:{ .lg .middle } [__Functions__](functions.md) +- :material-function-variant:{ .lg .middle } [__Functions__](organization/functions.md) Package a named block of code to run it at any time. - [**`def`**](functions.md#defining-a-function): - [`**kwargs`](functions.md#kwargs-dict) - [`*args`](functions.md#args-tuple) - [`defaults`](functions.md#default-values) - [`docstrings`](functions.md#docstrings) - [`parameters`](functions.md#parameters) - [`pass`](functions.md#pass-placeholder) - [`return`](functions.md#return-values) - - [`combining argument types`](functions.md#combining-categories) - [`keyword-only`](functions.md#keyword-only) - [`positional-only`](functions.md#positional-only) - [`type hints`](functions.md#type-hints) + [**`def`**](organization/functions.md#defining-a-function): + [`**kwargs`](organization/functions.md#kwargs-dict) + [`*args`](organization/functions.md#args-tuple) + [`defaults`](organization/functions.md#default-values) + [`docstrings`](organization/functions.md#docstrings) + [`parameters`](organization/functions.md#parameters) + [`pass`](organization/functions.md#pass-placeholder) + [`return`](organization/functions.md#return-values) + + [`combining argument types`](organization/functions.md#combining-categories) + [`keyword-only`](organization/functions.md#keyword-only) + [`positional-only`](organization/functions.md#positional-only) + [`type hints`](organization/functions.md#type-hints) {: data-advanced="true" } - [**`calling a function`**](functions.md#calling-a-function): - [`arguments`](functions.md#arguments) - [`keyword`](functions.md#by-keyword) - [`required`](functions.md#required) - [`return value`](functions.md#saving-the-return-value) - [`unpacking`](functions.md#unpacking) + [**`calling a function`**](organization/functions.md#calling-a-function): + [`arguments`](organization/functions.md#arguments) + [`keyword`](organization/functions.md#by-keyword) + [`required`](organization/functions.md#required) + [`return value`](organization/functions.md#saving-the-return-value) + [`unpacking`](organization/functions.md#unpacking) - [**`scope`**](functions.md#scope): - [`local vs global`](functions.md#local-vs-global-variables) + [**`scope`**](organization/functions.md#scope): + [`local vs global`](organization/functions.md#local-vs-global-variables) - [**`recursion`**](functions.md#recursion) + [**`recursion`**](organization/functions.md#recursion) {: data-advanced="true" } - [**`decorators`**](functions.md#decorators): - [`arguments`](functions.md#accepting-arguments) - [`identity`](functions.md#advanced-uses) - [`original function`](functions.md#returning-the-original-function) - [`stacking`](functions.md#advanced-uses) - [`wrapping`](functions.md#wrapping-the-call) + [**`decorators`**](organization/functions.md#decorators): + [`arguments`](organization/functions.md#accepting-arguments) + [`identity`](organization/functions.md#advanced-uses) + [`original function`](organization/functions.md#returning-the-original-function) + [`stacking`](organization/functions.md#advanced-uses) + [`wrapping`](organization/functions.md#wrapping-the-call) {: data-advanced="true" } - [**`generators`**](functions.md#generators): - [`generator expressions`](functions.md#generator-expressions) - [`memory`](functions.md#memory-efficiency) - [`yield`](functions.md#yield-vs-return) + [**`generators`**](organization/functions.md#generators): + [`generator expressions`](organization/functions.md#generator-expressions) + [`memory`](organization/functions.md#memory-efficiency) + [`yield`](organization/functions.md#yield-vs-return) {: data-advanced="true" } -- :material-package-variant:{ .lg .middle } [__Classes__](classes.md) +- :material-package-variant:{ .lg .middle } [__Classes__](organization/classes.md) Bundle related values and functions to a reusable blueprint for similar objects. - [**`class`**](classes.md#defining-a-class): - [`__init__()`](classes.md#the-__init__-method) - [`class attributes`](classes.md#class-attributes) - [`instance attributes`](classes.md#instance-attributes) - [`methods`](classes.md#object-methods) - [`self`](classes.md#the-self-parameter) - - [**`method decorators`**](classes.md#method-decorators): - [`@classmethod`](classes.md#classmethod) - [`@property`](classes.md#property) - [`@staticmethod`](classes.md#staticmethod) + [**`class`**](organization/classes.md#defining-a-class): + [`__init__()`](organization/classes.md#the-__init__-method) + [`class attributes`](organization/classes.md#class-attributes) + [`instance attributes`](organization/classes.md#instance-attributes) + [`methods`](organization/classes.md#object-methods) + [`self`](organization/classes.md#the-self-parameter) + + [**`method decorators`**](organization/classes.md#method-decorators): + [`@classmethod`](organization/classes.md#classmethod) + [`@property`](organization/classes.md#property) + [`@staticmethod`](organization/classes.md#staticmethod) {: data-advanced="true" } - [**`inheritance`**](classes.md#inheritance): - [`adding attributes and methods`](classes.md#adding-attributes-and-methods) - [`__init__()`](classes.md#overriding-__init__) - [`overriding`](classes.md#overriding-methods) - [`super()`](classes.md#using-super) + [**`inheritance`**](organization/classes.md#inheritance): + [`adding attributes and methods`](organization/classes.md#adding-attributes-and-methods) + [`__init__()`](organization/classes.md#overriding-__init__) + [`overriding`](organization/classes.md#overriding-methods) + [`super()`](organization/classes.md#using-super) - [`multiple inheritance`](classes.md#multiple-inheritance) + [`multiple inheritance`](organization/classes.md#multiple-inheritance) {: data-advanced="true" } - [**`polymorphism`**](classes.md#polymorphism): - [`inheritance`](classes.md#polymorphism-via-inheritance) - [`duplicate method names`](classes.md#duplicate-method-names) + [**`polymorphism`**](organization/classes.md#polymorphism): + [`inheritance`](organization/classes.md#polymorphism-via-inheritance) + [`duplicate method names`](organization/classes.md#duplicate-method-names) {: data-advanced="true" } - [**`encapsulation`**](classes.md#encapsulation): - [`@property`](classes.md#controlled-access-with-property) - [`double underscore`](classes.md#double-underscore) - [`single underscore`](classes.md#single-underscore) + [**`encapsulation`**](organization/classes.md#encapsulation): + [`@property`](organization/classes.md#controlled-access-with-property) + [`double underscore`](organization/classes.md#double-underscore) + [`single underscore`](organization/classes.md#single-underscore) {: data-advanced="true" } - [**`operator overloading`**](classes.md#operator-overloading): - [`__add__`](classes.md#arithmetic-with-__add__) - [`__eq__ and __lt__`](classes.md#comparing-with-__eq__-and-__lt__) + [**`operator overloading`**](organization/classes.md#operator-overloading): + [`__add__`](organization/classes.md#arithmetic-with-__add__) + [`__eq__ and __lt__`](organization/classes.md#comparing-with-__eq__-and-__lt__) {: data-advanced="true" } - [**`dataclasses`**](classes.md#dataclasses) + [**`dataclasses`**](organization/classes.md#dataclasses) {: data-advanced="true" } - [**`abstract base classes`**](classes.md#abstract-base-classes) + [**`abstract base classes`**](organization/classes.md#abstract-base-classes) {: data-advanced="true" }
@@ -480,52 +480,52 @@ hide:
-- :material-import:{ .lg .middle } [__Modules & Imports__](modules.md) +- :material-import:{ .lg .middle } [__Modules & Imports__](resources/modules.md) Splitting code across files, and using someone else's code. - [**`import`**](modules.md#importing-modules): - [`as`](modules.md#as) - [`from`](modules.md#from) - [`import`](modules.md#import) - [`import order`](modules.md#order-of-multiple-imports) - [`nested paths`](modules.md#nested-paths) - [`packages`](modules.md#packages) + [**`import`**](resources/modules.md#importing-modules): + [`as`](resources/modules.md#as) + [`from`](resources/modules.md#from) + [`import`](resources/modules.md#import) + [`import order`](resources/modules.md#order-of-multiple-imports) + [`nested paths`](resources/modules.md#nested-paths) + [`packages`](resources/modules.md#packages) - [**`your own module`**](modules.md#creating-your-own-module): - [`main guard`](modules.md#the-main-guard) + [**`your own module`**](resources/modules.md#creating-your-own-module): + [`main guard`](resources/modules.md#the-main-guard) - [**`module, package, library`**](modules.md#modules-vs-packages-vs-libraries) + [**`module, package, library`**](resources/modules.md#modules-vs-packages-vs-libraries) -- :material-file-document-outline:{ .lg .middle } [__Reading & Writing Files__](files.md) +- :material-file-document-outline:{ .lg .middle } [__Reading & Writing Files__](resources/files.md) Read and write text files on your computer. - [**`open`**](files.md#opening-and-closing-files): - [`modes`](files.md#modes-options) - [`paths`](files.md#file-paths) - [`with`](files.md#with) - - [**`read()`**](files.md#read): - [`existing`](files.md#r-read-existing) - [`functions`](files.md#functions) - [`modes`](files.md#modes) - [`read()`](files.md#whole-file) - [`readline()`](files.md#by-line) - [`readlines()`](files.md#by-line) - [`seek()`](files.md#seek-and-tell) - [`tell()`](files.md#seek-and-tell) - - [**`write()`**](files.md#write): - [`append`](files.md#a-append) - [`create`](files.md#x-create) - [`functions`](files.md#functions_1) - [`modes`](files.md#modes_1) - [`overwrite`](files.md#w-overwrite) - [`write()`](files.md#single-string) - [`writelines()`](files.md#multiple-strings) - - [**`related libraries`**](files.md#related-libraries) + [**`open`**](resources/files.md#opening-and-closing-files): + [`modes`](resources/files.md#modes-options) + [`paths`](resources/files.md#file-paths) + [`with`](resources/files.md#with) + + [**`read()`**](resources/files.md#read): + [`existing`](resources/files.md#r-read-existing) + [`functions`](resources/files.md#functions) + [`modes`](resources/files.md#modes) + [`read()`](resources/files.md#whole-file) + [`readline()`](resources/files.md#by-line) + [`readlines()`](resources/files.md#by-line) + [`seek()`](resources/files.md#seek-and-tell) + [`tell()`](resources/files.md#seek-and-tell) + + [**`write()`**](resources/files.md#write): + [`append`](resources/files.md#a-append) + [`create`](resources/files.md#x-create) + [`functions`](resources/files.md#functions_1) + [`modes`](resources/files.md#modes_1) + [`overwrite`](resources/files.md#w-overwrite) + [`write()`](resources/files.md#single-string) + [`writelines()`](resources/files.md#multiple-strings) + + [**`related libraries`**](resources/files.md#related-libraries)
@@ -535,87 +535,87 @@ hide:
-- :material-palette-outline:{ .lg .middle } [__Style__](style.md) +- :material-palette-outline:{ .lg .middle } [__Style__](practices/style.md) Readable Python code, and polished UI. - [**`PEP 8`**](style.md#pep-8-style-guide): - [`blank lines`](style.md#blank-lines) - [`docstrings`](style.md#docstrings) - [`naming`](style.md#naming) - [`whitespace`](style.md#whitespace) - - [`comments`](style.md#comments) - [`constants`](style.md#constants) - [`indentation`](style.md#indentation) - [`order`](style.md#file-order) - [`quote style`](style.md#quote-style) + [**`PEP 8`**](practices/style.md#pep-8-style-guide): + [`blank lines`](practices/style.md#blank-lines) + [`docstrings`](practices/style.md#docstrings) + [`naming`](practices/style.md#naming) + [`whitespace`](practices/style.md#whitespace) + + [`comments`](practices/style.md#comments) + [`constants`](practices/style.md#constants) + [`indentation`](practices/style.md#indentation) + [`order`](practices/style.md#file-order) + [`quote style`](practices/style.md#quote-style) {: data-advanced="true" } - [**`Linters, formatters`**](style.md#linters-and-formatters) + [**`Linters, formatters`**](practices/style.md#linters-and-formatters) - [**`Pythonic patterns`**](style.md#pythonic-patterns): - [`mutable defaults`](style.md#mutable-default-arguments) - [`is None`](style.md#is-none-instead-of-none) + [**`Pythonic patterns`**](practices/style.md#pythonic-patterns): + [`mutable defaults`](practices/style.md#mutable-default-arguments) + [`is None`](practices/style.md#is-none-instead-of-none) - [`truthy checks`](style.md#truthy-checks) - [`enumerate()`](style.md#enumerate-instead-of-range) + [`truthy checks`](practices/style.md#truthy-checks) + [`enumerate()`](practices/style.md#enumerate-instead-of-range) {: data-advanced="true" } - [**`Efficiency`**](style.md#efficiency) - [`big O`](style.md#big-o-notation) - [`common optimizations`](style.md#common-optimizations) - [`time`](style.md#time-and-space) - [`space`](style.md#time-and-space) + [**`Efficiency`**](practices/style.md#efficiency) + [`big O`](practices/style.md#big-o-notation) + [`common optimizations`](practices/style.md#common-optimizations) + [`time`](practices/style.md#time-and-space) + [`space`](practices/style.md#time-and-space) {: data-advanced="true" } - [**`Polished UX`**](style.md#polished-ux): - [`input validation`](style.md#input-validation) - [`menus`](style.md#menus) - [`randomize`](style.md#randomize-messages) - - [**`Polished UI`**](style.md#polished-ui): - [`background`](style.md#color-styling) - [`bold`](style.md#color-styling) - [`escape sequences`](style.md#escape-sequences) - [`color`](style.md#color-styling) - [`highlighting`](style.md#color-styling) - [`multi-line strings`](style.md#multi-line-strings) - [`formatting variables`](style.md#formatting-variables) - [`underline`](style.md#color-styling) - [`unicode symbols`](style.md#unicode-symbols) - [`dividers`](style.md#dividers) - [`boxes`](style.md#boxes) - [`progress bars`](style.md#progress-bars) - -- :material-bug-outline:{ .lg .middle } [__Errors__](errors.md) + [**`Polished UX`**](practices/style.md#polished-ux): + [`input validation`](practices/style.md#input-validation) + [`menus`](practices/style.md#menus) + [`randomize`](practices/style.md#randomize-messages) + + [**`Polished UI`**](practices/style.md#polished-ui): + [`background`](practices/style.md#color-styling) + [`bold`](practices/style.md#color-styling) + [`escape sequences`](practices/style.md#escape-sequences) + [`color`](practices/style.md#color-styling) + [`highlighting`](practices/style.md#color-styling) + [`multi-line strings`](practices/style.md#multi-line-strings) + [`formatting variables`](practices/style.md#formatting-variables) + [`underline`](practices/style.md#color-styling) + [`unicode symbols`](practices/style.md#unicode-symbols) + [`dividers`](practices/style.md#dividers) + [`boxes`](practices/style.md#boxes) + [`progress bars`](practices/style.md#progress-bars) + +- :material-bug-outline:{ .lg .middle } [__Errors__](practices/errors.md) Resolve bugs, read and utilize exceptions. - [**`kinds`**](errors.md#kinds-of-errors): - [`bugs`](errors.md) - [`exceptions`](errors.md) - [`logic errors`](errors.md#logic-errors) - [`runtime errors`](errors.md#runtime-errors) - [`syntax errors`](errors.md#syntax-errors) - - [**`fixing`**](errors.md#fixing-errors): - [`debugger tool`](errors.md#debugger-tool) - [`debugging strategies`](errors.md#debugging-strategies) - [`isolate problems`](errors.md#isolate-the-problem) - [`print debugging`](errors.md#print-debugging) - [`rubber duck debugging`](errors.md#read-it-out-loud) - [`syntax error message`](errors.md#reading-a-syntax-error-message) - [`testing`](errors.md#detect-errors-with-testing) - [`TODO / FIXME`](errors.md#flag-as-todofixme) - [`tracebacks`](errors.md#reading-a-traceback) - - [**`handling`**](errors.md#handling-errors): - [`assert`](errors.md#assert-a-condition) - [`else`](errors.md#finally) - [`finally`](errors.md#finally) - [`raise`](errors.md#raise-an-exception) - [`try/except`](errors.md#catch-with-tryexcept) + [**`kinds`**](practices/errors.md#kinds-of-errors): + [`bugs`](practices/errors.md) + [`exceptions`](practices/errors.md) + [`logic errors`](practices/errors.md#logic-errors) + [`runtime errors`](practices/errors.md#runtime-errors) + [`syntax errors`](practices/errors.md#syntax-errors) + + [**`fixing`**](practices/errors.md#fixing-errors): + [`debugger tool`](practices/errors.md#debugger-tool) + [`debugging strategies`](practices/errors.md#debugging-strategies) + [`isolate problems`](practices/errors.md#isolate-the-problem) + [`print debugging`](practices/errors.md#print-debugging) + [`rubber duck debugging`](practices/errors.md#read-it-out-loud) + [`syntax error message`](practices/errors.md#reading-a-syntax-error-message) + [`testing`](practices/errors.md#detect-errors-with-testing) + [`TODO / FIXME`](practices/errors.md#flag-as-todofixme) + [`tracebacks`](practices/errors.md#reading-a-traceback) + + [**`handling`**](practices/errors.md#handling-errors): + [`assert`](practices/errors.md#assert-a-condition) + [`else`](practices/errors.md#finally) + [`finally`](practices/errors.md#finally) + [`raise`](practices/errors.md#raise-an-exception) + [`try/except`](practices/errors.md#catch-with-tryexcept)
diff --git a/docs/libraries/beautifulsoup.md b/docs/libraries/beautifulsoup.md index 3cb65da..17b92b4 100644 --- a/docs/libraries/beautifulsoup.md +++ b/docs/libraries/beautifulsoup.md @@ -219,7 +219,7 @@ print(tag.attrs) ## Extracting structured data -A page is rarely useful one tag at a time — the real value of `find_all()` is looping over its results to build a plain Python list, the same list-of-dicts shape as [Collections](../collections.md#dictionaries)' own snake catalog, ready to filter, sort, or save to a [CSV](csv.md) or [JSON](json.md) file. +A page is rarely useful one tag at a time — the real value of `find_all()` is looping over its results to build a plain Python list, the same list-of-dicts shape as [Collections](../types/collections.md#dictionaries)' own snake catalog, ready to filter, sort, or save to a [CSV](csv.md) or [JSON](json.md) file. ```python-ref snakes = [] @@ -273,7 +273,7 @@ BeautifulSoup only parses HTML that's already in hand — pairing it with [reque ### Common tasks -Each function below wraps a single `find`/`find_all` call in a [function](../functions.md), returning a plain [list](../collections.md#lists) built with a [list comprehension](../collections.md#list-comprehension) — store the result in a variable and use it like any other value. +Each function below wraps a single `find`/`find_all` call in a [function](../organization/functions.md), returning a plain [list](../types/collections.md#lists) built with a [list comprehension](../types/collections.md#list-comprehension) — store the result in a variable and use it like any other value. ```python-ref get_title(soup) # "Ball python" diff --git a/docs/libraries/collections.md b/docs/libraries/collections.md index da92211..26b0185 100644 --- a/docs/libraries/collections.md +++ b/docs/libraries/collections.md @@ -13,10 +13,10 @@ description: >- !!! note "Not the same as the Collections page" This page covers the **`collections` module** — extra container types imported with `from collections import ...`. For the built-in `list`, `dict`, `tuple`, and `set` types - themselves, see [Collections](../collections.md). + themselves, see [Collections](../types/collections.md). The **`collections`** module adds specialized containers with added functionality on top of the -built-in [`str`](../types.md#strings) [`list`](../collections.md#lists) [`dict`](../collections.md#dictionaries) [`tuple`](../collections.md#tuples) and [`set`](../collections.md#sets). +built-in [`str`](../types/basics.md#strings) [`list`](../types/collections.md#lists) [`dict`](../types/collections.md#dictionaries) [`tuple`](../types/collections.md#tuples) and [`set`](../types/collections.md#sets).
diff --git a/docs/libraries/numpy.md b/docs/libraries/numpy.md index a42f472..d0fcd64 100644 --- a/docs/libraries/numpy.md +++ b/docs/libraries/numpy.md @@ -101,11 +101,11 @@ print(lengths_m) | Python `for` loop | O(n) | — | | Vectorized | O(n) (same class, smaller constant) | — | - A Python `for` loop over a list and a vectorized NumPy operation both touch every element once — O(n) either way, the same [Big O](../style.md#big-o-notation) class. + A Python `for` loop over a list and a vectorized NumPy operation both touch every element once — O(n) either way, the same [Big O](../practices/style.md#big-o-notation) class. The speed difference is a constant factor, not the order of growth: each pass of a Python loop pays the interpreter's per-iteration overhead, while a vectorized operation runs its loop once, in compiled C, underneath a single Python call. That overhead is small per element but adds up — the larger the array, the bigger the gap, even though neither approach's growth rate has changed. - See [Efficiency](../style.md#efficiency) for why this distinction matters. + See [Efficiency](../practices/style.md#efficiency) for why this distinction matters. ### Aggregating an array diff --git a/docs/libraries/pillow.md b/docs/libraries/pillow.md index 6170e96..a716449 100644 --- a/docs/libraries/pillow.md +++ b/docs/libraries/pillow.md @@ -326,7 +326,7 @@ img.save("shapes.png") ``` ??? tip "Drawing with objects" - Once a drawing gets complicated, it's common to wrap each thing you're drawing in its own class — an object that stores its own position/size/color, and knows how to draw itself given a drawing context. Nothing here is Pillow-specific: it's the same pattern covered in [Classes](../classes.md) — bundling data with the behavior that acts on it — just applied to a shape instead of a snake. A calling function loops over a list of these objects and calls `.draw()` on each, so building a complex image — dozens of randomly placed shapes, say, using the `random` module — is just a loop appending new `Shape` objects rather than dozens of manual `draw_context` calls. + Once a drawing gets complicated, it's common to wrap each thing you're drawing in its own class — an object that stores its own position/size/color, and knows how to draw itself given a drawing context. Nothing here is Pillow-specific: it's the same pattern covered in [Classes](../organization/classes.md) — bundling data with the behavior that acts on it — just applied to a shape instead of a snake. A calling function loops over a list of these objects and calls `.draw()` on each, so building a complex image — dozens of randomly placed shapes, say, using the `random` module — is just a loop appending new `Shape` objects rather than dozens of manual `draw_context` calls. ```python-ref class Shape: @@ -585,7 +585,7 @@ img.convert("RGB").save("snake.jpg") ## ImageSequence module -An animated GIF is really a whole stack of images shown one after another. `Image.open()` only gives you the first frame by default — `ImageSequence` lets a [`for` loop](../loops.md) step through every frame in order. +An animated GIF is really a whole stack of images shown one after another. `Image.open()` only gives you the first frame by default — `ImageSequence` lets a [`for` loop](../flow/loops.md) step through every frame in order. ```python-ref from PIL import Image, ImageSequence @@ -619,7 +619,7 @@ for frame in ImageSequence.Iterator(gif): ## Putting it together -Pillow doesn't need anything special to combine with the rest of Python — a function wrapping one transformation, called from an `if`/`elif` chosen by [user input](../conditionals.md), looped until the user's done, is enough to build a small interactive tool out of the operations above. +Pillow doesn't need anything special to combine with the rest of Python — a function wrapping one transformation, called from an `if`/`elif` chosen by [user input](../flow/conditionals.md), looped until the user's done, is enough to build a small interactive tool out of the operations above. ```python-ref def apply_filter(img, choice): @@ -635,7 +635,7 @@ def apply_filter(img, choice): ### An interactive filter tool -Combines a function, an `if`/`elif` chain, and a `while` loop — nothing here is Pillow-specific. Each piece here is something covered elsewhere on this site — a [function](../functions.md) wrapping one transformation, an [`if`/`elif` chain](../conditionals.md) picking which one to run, and a [`while` loop](../loops.md#while-loops) that keeps asking until the user's satisfied. Pillow itself only shows up inside `apply_filter`. +Combines a function, an `if`/`elif` chain, and a `while` loop — nothing here is Pillow-specific. Each piece here is something covered elsewhere on this site — a [function](../organization/functions.md) wrapping one transformation, an [`if`/`elif` chain](../flow/conditionals.md) picking which one to run, and a [`while` loop](../flow/loops.md#while-loops) that keeps asking until the user's satisfied. Pillow itself only shows up inside `apply_filter`. ```python-ref while True: diff --git a/docs/libraries/pytest.md b/docs/libraries/pytest.md index af6f4f4..ad248a0 100644 --- a/docs/libraries/pytest.md +++ b/docs/libraries/pytest.md @@ -189,7 +189,7 @@ def test_length_is_positive(species, length_ft): ## Testing for exceptions -`pytest.raises()` is a context manager that asserts the code inside its `with` block raises a specific exception — a way to test the [`try`/`except`](../errors.md#catch-with-tryexcept) paths in your own code, not just the successful ones. +`pytest.raises()` is a context manager that asserts the code inside its `with` block raises a specific exception — a way to test the [`try`/`except`](../practices/errors.md#catch-with-tryexcept) paths in your own code, not just the successful ones. ```python-ref def test_invalid_length_raises(): diff --git a/docs/libraries/re.md b/docs/libraries/re.md index b9b7e1b..4521dce 100644 --- a/docs/libraries/re.md +++ b/docs/libraries/re.md @@ -74,7 +74,7 @@ print(match.group()) Calling `re.search()` (or `.findall()`, `.sub()`, etc.) with a raw pattern string repeats the same compilation work internally on every call, even when the pattern never changes — across a loop of n calls, that's n compilations of the same pattern. Compiling it once with `re.compile()` above the loop and calling `.search()` on the result instead does that work exactly once, however many times the loop runs. - See [Efficiency](../style.md#efficiency) for why this distinction matters. + See [Efficiency](../practices/style.md#efficiency) for why this distinction matters. ??? run "Run a searching example" All the examples above, combined into one script: diff --git a/docs/libraries/requests.md b/docs/libraries/requests.md index 68e6d9a..0e33584 100644 --- a/docs/libraries/requests.md +++ b/docs/libraries/requests.md @@ -98,7 +98,7 @@ print("request succeeded") ### Parsing JSON -`.json()` converts a JSON response body directly into a Python `dict` or `list`. Most web APIs send their data back as JSON — text formatted so it maps directly onto Python's own `dict`/`list` structures, which is why `.json()` needs no extra parsing step. Once converted, the result works exactly like any other [dict](../collections.md#dictionaries) or [list](../collections.md#lists) you'd build by hand. +`.json()` converts a JSON response body directly into a Python `dict` or `list`. Most web APIs send their data back as JSON — text formatted so it maps directly onto Python's own `dict`/`list` structures, which is why `.json()` needs no extra parsing step. Once converted, the result works exactly like any other [dict](../types/collections.md#dictionaries) or [list](../types/collections.md#lists) you'd build by hand. ```python-ref response = requests.get("https://jsonplaceholder.typicode.com/posts/1") @@ -198,7 +198,7 @@ print(response.json()) ## Handling request errors -A network call can fail in ways that have nothing to do with your code — the [Errors](../errors.md#catch-with-tryexcept) page covers `try`/`except` in general; a couple of exceptions are specific to `requests`. +A network call can fail in ways that have nothing to do with your code — the [Errors](../practices/errors.md#catch-with-tryexcept) page covers `try`/`except` in general; a couple of exceptions are specific to `requests`. | Exception | Happens when | |-----------|---------------| diff --git a/docs/libraries/turtle.md b/docs/libraries/turtle.md index 249bf33..7f8dd46 100644 --- a/docs/libraries/turtle.md +++ b/docs/libraries/turtle.md @@ -204,7 +204,7 @@ A related but separate setting — `speed(n)` controls how fast each individual `tracer()` also accepts two numbers, `tracer(n, delay)` — show only every `n`-th update, with `delay` milliseconds between them, instead of turning animation off completely. Useful for speeding up something slow and complex without losing the animation altogether. ??? tip "no_animation() block" - A context manager wrapping the same idea as `tracer(False)`/`tracer(True)` — animation is off for whatever runs inside the block, then back on (and shown) once it exits. The same `with` pattern as [opening a file](../files.md#with), applied to animation instead of a file handle. + A context manager wrapping the same idea as `tracer(False)`/`tracer(True)` — animation is off for whatever runs inside the block, then back on (and shown) once it exits. The same `with` pattern as [opening a file](../resources/files.md#with), applied to animation instead of a file handle. ```python-ref with no_animation(): @@ -383,7 +383,7 @@ x, y = ball # unpack like any other tuple — 3, 5 ``` ??? tip "Reassigning from inside a function" - Reassigning a global variable's name from inside a function needs `global`, covered on [Functions](../functions.md#local-vs-global-variables) — a game typically has at least one small function whose only job is reassigning a position or direction this way. *Mutating* something in place instead (`trail.append(...)`, `paddles[1] = paddles[1] + Vec2D(0, 20)`, both from "Many positions at once" below) doesn't need `global`, since the name itself is never reassigned — only reassignment does. + Reassigning a global variable's name from inside a function needs `global`, covered on [Functions](../organization/functions.md#local-vs-global-variables) — a game typically has at least one small function whose only job is reassigning a position or direction this way. *Mutating* something in place instead (`trail.append(...)`, `paddles[1] = paddles[1] + Vec2D(0, 20)`, both from "Many positions at once" below) doesn't need `global`, since the name itself is never reassigned — only reassignment does. ```python-ref aim = Vec2D(0, -10) @@ -411,7 +411,7 @@ forward(5) #### Many positions at once { data-card-link="skip" } -A game's state is rarely just one lone position — a trail that grows over time, or several independent entities tracked at once. Both build on the same list/dict operations covered on [Collections](../collections.md). +A game's state is rarely just one lone position — a trail that grows over time, or several independent entities tracked at once. Both build on the same list/dict operations covered on [Collections](../types/collections.md). ```python-ref trail = [Vec2D(10, 0)] diff --git a/docs/classes.md b/docs/organization/classes.md similarity index 96% rename from docs/classes.md rename to docs/organization/classes.md index b9e94f8..ced01e4 100644 --- a/docs/classes.md +++ b/docs/organization/classes.md @@ -8,7 +8,7 @@ description: >-
-A **class** bundles related data together with the behavior (methods) that acts on it, instead of keeping them separate. A [dictionary](collections.md#dictionaries) can already hold a snake's data as key-value pairs — a class goes one step further, pairing that data with the functions that work on it. Structuring code this way is called **object-oriented programming (OOP)**. +A **class** bundles related data together with the behavior (methods) that acts on it, instead of keeping them separate. A [dictionary](../types/collections.md#dictionaries) can already hold a snake's data as key-value pairs — a class goes one step further, pairing that data with the functions that work on it. Structuring code this way is called **object-oriented programming (OOP)**. | Concept | Example | What it is | |---------|---------|------------| @@ -142,7 +142,7 @@ For a value every object should share instead of holding its own copy, see [clas | Plain instance | — | O(n) | | `__slots__` | — | O(n) (same class, smaller constant) | - Each instance normally keeps its attributes in a per-object `__dict__`, which costs some [memory](style.md#time-and-space) on top of the attribute values themselves — usually not worth worrying about, but it adds up when a program holds thousands or millions of instances at once. `__slots__` trades that flexibility for a fixed, lighter attribute layout: + Each instance normally keeps its attributes in a per-object `__dict__`, which costs some [memory](../practices/style.md#time-and-space) on top of the attribute values themselves — usually not worth worrying about, but it adds up when a program holds thousands or millions of instances at once. `__slots__` trades that flexibility for a fixed, lighter attribute layout: ```python-ref class Snake: @@ -155,7 +155,7 @@ For a value every object should share instead of holding its own copy, see [clas An instance built from this class can no longer get a new attribute added after creation — `ball.venomous = False` raises `AttributeError`, since there's no `__dict__` left for it to go into. - See [Efficiency](style.md#efficiency) for why this distinction matters. + See [Efficiency](../practices/style.md#efficiency) for why this distinction matters.
@@ -215,7 +215,7 @@ print(burmese.kingdom) # "Animalia" — unaffected ``` ??? tip "Modify & delete attributes" - Assign to `object.attribute` to change it after creation — an object is **mutable**, so this changes it in place, the same as [updating an item in a list](collections.md#access-and-update-items). That also means a second variable pointing at the same object sees the change too: `twin = ball` doesn't copy `ball`, it just gives the same object a second name. + Assign to `object.attribute` to change it after creation — an object is **mutable**, so this changes it in place, the same as [updating an item in a list](../types/collections.md#access-and-update-items). That also means a second variable pointing at the same object sees the change too: `twin = ball` doesn't copy `ball`, it just gives the same object a second name. `del object.attribute` removes a single attribute; `del object` removes the object itself. diff --git a/docs/functions.md b/docs/organization/functions.md similarity index 94% rename from docs/functions.md rename to docs/organization/functions.md index 4b095fd..14105a1 100644 --- a/docs/functions.md +++ b/docs/organization/functions.md @@ -105,7 +105,7 @@ message = describe("ball") # "a ball python" is the return value, so now A function is first defined. After it's defined, you can [call the function](#calling-a-function) whenever you need to run it. -The function definition line contains **`def`**, a **function name** (follows the same [naming rules as variables](foundations.md#naming-variables)), **parentheses** holding zero or more **parameters**, and a **colon**. +The function definition line contains **`def`**, a **function name** (follows the same [naming rules as variables](../start/foundations.md#naming-variables)), **parentheses** holding zero or more **parameters**, and a **colon**. Under it is an indented **body**: the block of code that runs when the function is [called](#calling-a-function). @@ -175,7 +175,7 @@ describe("ball") # second parameter is not given, so length_ft is the d #### *args tuple -`*args` collects any number of positional arguments into a single [tuple](collections.md#tuples), so a function can accept as many as the caller passes instead of a fixed list of parameters. `*args` is the conventional name, but any name after `*` works. +`*args` collects any number of positional arguments into a single [tuple](../types/collections.md#tuples), so a function can accept as many as the caller passes instead of a fixed list of parameters. `*args` is the conventional name, but any name after `*` works. ```python-ref def total_length(*args): @@ -186,7 +186,7 @@ total_length(5, 12, 8) # 25 #### **kwargs dict -`**kwargs` collects any number of keyword arguments into a single [dict](collections.md#dictionaries), so a function can accept as many `name=value` pairs as the caller passes instead of a fixed list of parameters. `**kwargs` is the conventional name, but any name after `**` works. +`**kwargs` collects any number of keyword arguments into a single [dict](../types/collections.md#dictionaries), so a function can accept as many `name=value` pairs as the caller passes instead of a fixed list of parameters. `**kwargs` is the conventional name, but any name after `**` works. ```python-ref def describe(**details): @@ -275,7 +275,7 @@ result = find_species("cobra") # None — the function fell through without a #### Multiple values { data-card-link="skip" } -`return` followed by several values separated by commas [packs](collections.md#packing-and-unpacking) them into a tuple as a single return value. The caller unpacks that tuple to use the values separately — see [multiple values](#multiple-values_1) under calling a function. +`return` followed by several values separated by commas [packs](../types/collections.md#packing-and-unpacking) them into a tuple as a single return value. The caller unpacks that tuple to use the values separately — see [multiple values](#multiple-values_1) under calling a function. ```python-ref def describe(species, length_ft): @@ -333,7 +333,7 @@ def describe(species): ### Docstrings -A triple-quoted string as a function's first line documents what it does — most editors show it automatically when you use the function elsewhere. A **docstring** is the same triple-quoted-string trick covered on the [Foundations](foundations.md#multi-line-comments-with) page, but placed as the very first line inside a function specifically to document it. Unlike a regular comment, Python actually stores a docstring (as the function's `__doc__` attribute) rather than discarding it — which is how editors are able to show it in a tooltip when you call the function elsewhere, without you needing to go find the definition. +A triple-quoted string as a function's first line documents what it does — most editors show it automatically when you use the function elsewhere. A **docstring** is the same triple-quoted-string trick covered on the [Foundations](../start/foundations.md#multi-line-comments-with) page, but placed as the very first line inside a function specifically to document it. Unlike a regular comment, Python actually stores a docstring (as the function's `__doc__` attribute) rather than discarding it — which is how editors are able to show it in a tooltip when you call the function elsewhere, without you needing to go find the definition. Short, single-line docstrings are common for simple functions: @@ -436,7 +436,7 @@ message = describe("ball") # "a ball python" — stored, not printed #### Multiple values { data-card-link="skip" } -A function that [returns multiple values packed into a tuple](#multiple-values) can have them unpacked straight into multiple variables in one line at the call site. `name, length = describe(...)` unpacks the returned tuple, matching each variable to the tuple's items by position — the same as [unpacking any other tuple](collections.md#packing-and-unpacking). The number of variables on the left has to match the number of values returned. +A function that [returns multiple values packed into a tuple](#multiple-values) can have them unpacked straight into multiple variables in one line at the call site. `name, length = describe(...)` unpacks the returned tuple, matching each variable to the tuple's items by position — the same as [unpacking any other tuple](../types/collections.md#packing-and-unpacking). The number of variables on the left has to match the number of values returned. ```python-ref def describe(species, length_ft): @@ -550,7 +550,7 @@ Every recursive function needs two parts: ``` ??? tip "Recursion vs. a loop" - Anything recursion can do, a loop can do too — recursion is rarely the only option, just sometimes the more natural fit. It reads most naturally for problems already defined in terms of themselves, like a [nested dictionary](collections.md#dictionaries) of arbitrary depth, where the number of levels isn't known ahead of time. For a simple countdown like the one above, a `while` loop is just as clear and doesn't risk a `RecursionError`. + Anything recursion can do, a loop can do too — recursion is rarely the only option, just sometimes the more natural fit. It reads most naturally for problems already defined in terms of themselves, like a [nested dictionary](../types/collections.md#dictionaries) of arbitrary depth, where the number of levels isn't known ahead of time. For a simple countdown like the one above, a `while` loop is just as clear and doesn't risk a `RecursionError`. ```python-ref n = 3 @@ -570,9 +570,9 @@ Every recursive function needs two parts: Every call a function makes — recursive or not — adds a frame to the call stack and holds that call's local variables until it returns. A recursive function keeps every call's frame alive until the base case is reached, so its space cost is O(n) for n levels of recursion. That's why the `RecursionError` exists — Python caps how deep the stack can grow before it runs out of room. - A loop reuses the same frame each pass, O(1) [space](style.md#time-and-space). + A loop reuses the same frame each pass, O(1) [space](../practices/style.md#time-and-space). - See [Efficiency](style.md#efficiency) for why this distinction matters. + See [Efficiency](../practices/style.md#efficiency) for why this distinction matters.
@@ -865,7 +865,7 @@ next(counter) # 2 ### Generator expressions -Parentheses instead of brackets turn a [list comprehension](collections.md#list-comprehension) into a generator expression — same filtering and transforming syntax, but values are produced lazily instead of built into a list all at once. +Parentheses instead of brackets turn a [list comprehension](../types/collections.md#list-comprehension) into a generator expression — same filtering and transforming syntax, but values are produced lazily instead of built into a list all at once. `doubled_list` below is an actual `list` — `[10, 24, 16]`, every value already computed — so it supports indexing, `len()`, and looping over more than once. `doubled_gen` is a `generator` — nothing has been computed yet, and it only supports stepping forward once with `next()` or a `for` loop, the same [list vs. generator](#generator-vs-a-regular-function) tradeoff covered above. diff --git a/docs/errors.md b/docs/practices/errors.md similarity index 89% rename from docs/errors.md rename to docs/practices/errors.md index cc69bea..8022176 100644 --- a/docs/errors.md +++ b/docs/practices/errors.md @@ -77,16 +77,16 @@ Think about what programming concepts the failing line is using (data type, loop | Kind of runtime error | Happens when | Check for | |-------|---------------|-----------| | **`AssertionError`** | An [`assert`](#assert-a-condition) statement's condition was `False` | | -| **`AttributeError`** | Calling a method or attribute that doesn't exist on that object | | +| **`AttributeError`** | Calling a method or attribute that doesn't exist on that object | | | **`FileNotFoundError`** | Trying to open a file that doesn't exist at that path | | -| **`ImportError`** | Importing a name that doesn't exist in a module that *was* found | | +| **`ImportError`** | Importing a name that doesn't exist in a module that *was* found | | | **`IndexError`** | Looking up an index that doesn't exist — in a list, tuple, or string | | -| **`KeyError`** | Looking up a dict key that doesn't exist | | +| **`KeyError`** | Looking up a dict key that doesn't exist | | | **`ModuleNotFoundError`** | Importing a module that can't be found| | -| **`NameError`** | Using a variable that hasn't been assigned yet | | -| **`RecursionError`** | A function calls itself too many times without ever reaching a base case | | -| **`TypeError`** | Using a value the wrong way for its type, or calling a function with the wrong number of arguments | | -| **`UnboundLocalError`** | A local variable used before it's assigned | | +| **`NameError`** | Using a variable that hasn't been assigned yet | | +| **`RecursionError`** | A function calls itself too many times without ever reaching a base case | | +| **`TypeError`** | Using a value the wrong way for its type, or calling a function with the wrong number of arguments | | +| **`UnboundLocalError`** | A local variable used before it's assigned | | | **`ValueError`** | The argument is the right *type*, but not a valid *value* for what's being done with it | | | **`ZeroDivisionError`** | Dividing by zero | | @@ -185,7 +185,7 @@ if length < 1 and length > 20: # "If length is under 1 and length print(type(length), length) # confirm what a value actually is, not what you assumed it was ``` -Sprinkle `print()` calls between the lines you suspect, showing a variable's value (and [`type()`](types.md), if you're not sure) at that exact point in the run. This narrows down *where* your assumption about the code stopped matching reality — especially useful when nothing crashes and you're just staring at a wrong final answer, so there's no traceback pointing anywhere. Delete the `print()` calls once you've found the problem. +Sprinkle `print()` calls between the lines you suspect, showing a variable's value (and [`type()`](../types/basics.md), if you're not sure) at that exact point in the run. This narrows down *where* your assumption about the code stopped matching reality — especially useful when nothing crashes and you're just staring at a wrong final answer, so there's no traceback pointing anywhere. Delete the `print()` calls once you've found the problem. #### Isolate the problem { .pt-fake-h3 } @@ -238,7 +238,7 @@ A **debugger** is a tool built into most code editors that lets you pause a runn | Control | What it does | Use it when | |---------|---------------|-------------| | **Inspect variables** | Shows the current value of every variable while paused | You want to watch exactly when a variable becomes wrong, instead of guessing | - | **Step Into** | Jumps inside the [function](functions.md) being called, so you can watch it run line by line | You want to see exactly what a function does | + | **Step Into** | Jumps inside the [function](../organization/functions.md) being called, so you can watch it run line by line | You want to see exactly what a function does | | **Step Over** | Runs the current line, then pauses on the next one, without entering any function it calls | You trust the function works and don't need to see inside it | | **Step Out** | Finishes the current function, then pauses back where it was called from | You stepped into a function but have seen enough and want to jump back out | | **Continue/Resume** (▶) | Runs until the next breakpoint, or finishes if there are none left | You're done inspecting the current pause point and want to jump ahead | @@ -300,7 +300,7 @@ def test_missing_species_returns_none(): assert get_length("reticulated python", lengths) is None ``` -[pytest](libraries/pytest.md) is the standard tool for this in Python — a function starting with `test_` is one check, and inside it `assert` states what should be true. Running the file reports exactly which checks passed and which failed, the same way `python` reports which line of your code raised an error. +[pytest](../libraries/pytest.md) is the standard tool for this in Python — a function starting with `test_` is one check, and inside it `assert` states what should be true. Running the file reports exactly which checks passed and which failed, the same way `python` reports which line of your code raised an error. Tests are especially good at catching [logic errors](#logic-errors) — where the only way to notice something's wrong is comparing the actual output against what you expected. A test does that comparison automatically, instead of relying on you to notice by eye. diff --git a/docs/style.md b/docs/practices/style.md similarity index 85% rename from docs/style.md rename to docs/practices/style.md index ac6fd13..0f6af68 100644 --- a/docs/style.md +++ b/docs/practices/style.md @@ -30,10 +30,10 @@ Python runs styled and unstyled code identically, so following PEP 8 doesn't mak A Python file conventionally follows the same layout, top to bottom — a linter won't flag this on its own the way it does most of PEP 8, since it's a convention about where things go rather than a formatting rule.[^order-pep8] 1. **Module docstring** — what the file does -2. **[Imports](modules.md#importing-modules)** — standard library, then third-party, then local +2. **[Imports](../resources/modules.md#importing-modules)** — standard library, then third-party, then local 3. **Constants** — `ALL_CAPS` values used throughout the file 4. **Functions and classes** — the file's actual logic -5. **[The `if __name__ == "__main__":` guard](modules.md#the-main-guard)** — the code that runs when the file is executed +5. **[The `if __name__ == "__main__":` guard](../resources/modules.md#the-main-guard)** — the code that runs when the file is executed [^order-pep8]: The first three steps are PEP 8. Where functions/classes and the main guard fall isn't PEP 8 — but it is the convention the rest of the Python community has settled on. @@ -69,7 +69,7 @@ if __name__ == "__main__": ### Naming -A variable name should say what it holds — `length_ft` over `l`, `species_list` over `data`. `snake_case` and the other naming rules are covered on the [Foundations](foundations.md#naming-variables) page; this is about picking a *meaningful* name within those rules, not just a valid one. +A variable name should say what it holds — `length_ft` over `l`, `species_list` over `data`. `snake_case` and the other naming rules are covered on the [Foundations](../start/foundations.md#naming-variables) page; this is about picking a *meaningful* name within those rules, not just a valid one. ```python-ref l = 4.5 # what is l? @@ -104,7 +104,7 @@ print('it\'s a ball python') # works, but harder to read ### Docstrings -A triple-quoted string as the first line of a function or a file documents what it does — the underlying trick is the same [multi-line comment](foundations.md#multi-line-comments-with) covered on Foundations, just placed specifically as the first line. +A triple-quoted string as the first line of a function or a file documents what it does — the underlying trick is the same [multi-line comment](../start/foundations.md#multi-line-comments-with) covered on Foundations, just placed specifically as the first line. ```python-ref def is_unusually_long(length_ft): @@ -129,7 +129,7 @@ def is_unusually_long(species, length_ft): return length_ft > 5 ``` -Full rules on the [Functions](functions.md#docstrings) page. +Full rules on the [Functions](../organization/functions.md#docstrings) page. Placed as the very first line of a file instead, the same trick becomes a **module docstring** — documenting the file as a whole rather than a single function, and a common place to note who wrote it and when. @@ -339,21 +339,21 @@ Representing **O**rder of growth, the standard way to describe *how time and spa | While using | Instead of | **do this** | Because of | |---|---|---|---| -| [Strings](types.md#combine) | `+=` in a loop
O(n²) | `.join()`
O(n) | Big O | -| [Lists](collections.md#create) | `result = result + [item]` in a loop
O(n²) | `result.append(item)`
O(n) | Big O | -| [Lists](collections.md#inspect) | Counting items in a loop
O(n) | `len()`
O(1) | Big O | -| [Dictionaries](collections.md#dictionaries) | Checking `in` then indexing (two lookups)
O(1) | `.get()` (one lookup)
O(1) | Redundant work | -| [Sets](collections.md#sets) | `in` on a list or tuple
O(n) | `in` on a set or dict
O(1) average | Big O | -| [Lists](collections.md#lists) | `sorted()`, when the original doesn't need to survive
O(n) space | `sort()`
O(1) space | Big O | -| [Sets](collections.md#sets) | Checking every item against every other item for a duplicate, a loop nested inside another loop
O(n²) | Converting to a set to check for duplicates
O(n) | Big O | -| [Dictionaries](collections.md#dictionaries) | A list of `(key, value)` tuples, searched by hand
O(n) | A dict
O(1) | Big O | -| [Lists](collections.md#lists) | `insert(0, x)` / `pop(0)`
O(n) | `append()` / `pop()` (or `deque` for the front)
O(1) | Amortized | -| [By line](files.md#by-line) | `.read()` / `.readlines()` on a large file
O(n) space | A loop, line by line
O(1) space | Big O | -| [Recursion](functions.md#recursion) | Deep recursion
O(n) space | A loop
O(1) space | Big O | -| [Array operations](libraries/numpy.md#array-operations) | A Python loop over an array
O(n) | A vectorized NumPy operation, smaller constant
O(n) | Constant factor | -| [Searching for a pattern](libraries/re.md#searching-for-a-pattern) | Recompiling a regex pattern every pass
O(n) | `re.compile()` once, reused
O(1) | Redundant work | +| [Strings](../types/basics.md#combine) | `+=` in a loop
O(n²) | `.join()`
O(n) | Big O | +| [Lists](../types/collections.md#create) | `result = result + [item]` in a loop
O(n²) | `result.append(item)`
O(n) | Big O | +| [Lists](../types/collections.md#inspect) | Counting items in a loop
O(n) | `len()`
O(1) | Big O | +| [Dictionaries](../types/collections.md#dictionaries) | Checking `in` then indexing (two lookups)
O(1) | `.get()` (one lookup)
O(1) | Redundant work | +| [Sets](../types/collections.md#sets) | `in` on a list or tuple
O(n) | `in` on a set or dict
O(1) average | Big O | +| [Lists](../types/collections.md#lists) | `sorted()`, when the original doesn't need to survive
O(n) space | `sort()`
O(1) space | Big O | +| [Sets](../types/collections.md#sets) | Checking every item against every other item for a duplicate, a loop nested inside another loop
O(n²) | Converting to a set to check for duplicates
O(n) | Big O | +| [Dictionaries](../types/collections.md#dictionaries) | A list of `(key, value)` tuples, searched by hand
O(n) | A dict
O(1) | Big O | +| [Lists](../types/collections.md#lists) | `insert(0, x)` / `pop(0)`
O(n) | `append()` / `pop()` (or `deque` for the front)
O(1) | Amortized | +| [By line](../resources/files.md#by-line) | `.read()` / `.readlines()` on a large file
O(n) space | A loop, line by line
O(1) space | Big O | +| [Recursion](../organization/functions.md#recursion) | Deep recursion
O(n) space | A loop
O(1) space | Big O | +| [Array operations](../libraries/numpy.md#array-operations) | A Python loop over an array
O(n) | A vectorized NumPy operation, smaller constant
O(n) | Constant factor | +| [Searching for a pattern](../libraries/re.md#searching-for-a-pattern) | Recompiling a regex pattern every pass
O(n) | `re.compile()` once, reused
O(1) | Redundant work | | [try/except](errors.md#catch-with-tryexcept) | Checking first, when failure is rare
O(1) | `try`/`except`, cheaper when it succeeds
O(1) | Constant factor | -| [Instance attributes](classes.md#instance-attributes) | Many plain instances
O(n) memory | `__slots__`, smaller constant
O(n) memory | Constant factor | +| [Instance attributes](../organization/classes.md#instance-attributes) | Many plain instances
O(n) memory | `__slots__`, smaller constant
O(n) memory | Constant factor | @@ -395,7 +395,7 @@ while True: print(f"That's about {age * 7} in human years.") ``` -Using a [string validate method](types.md#validate) is another other way to catch this — checking the string *before* converting it, instead of attempting the conversion and catching the failure after: +Using a [string validate method](../types/basics.md#validate) is another other way to catch this — checking the string *before* converting it, instead of attempting the conversion and catching the failure after: ```python-ref species = input("Enter a species name: ") @@ -417,7 +417,7 @@ print(f"\nScanning... {species} detected.") ### Menus -Let the user pick from a short list of options with `input()` and [`match`/`case`](conditionals.md#match-case) — a clear list of options to choose from, instead of leaving them to guess what to type. +Let the user pick from a short list of options with `input()` and [`match`/`case`](../flow/conditionals.md#match-case) — a clear list of options to choose from, instead of leaving them to guess what to type. #### Simple input @@ -425,7 +425,7 @@ A menu is easiest to validate when each option is a single number or letter inst text — there's only a handful of possible answers to check against, as in every example below. Save the deeper validation for input that has to be open-ended, like a species name or a measurement — and even there, don't assume the user typed it in the exact case or format -expected. Normalize the answer first with [`.strip()`](types.md#modify), `.lower()`, or +expected. Normalize the answer first with [`.strip()`](../types/basics.md#modify), `.lower()`, or `.title()`, instead of rejecting anything that doesn't match exactly. #### Single choice @@ -494,7 +494,7 @@ match choice: print("Goodbye!") ``` -Comparing with [`.strip()`](types.md#modify) and `.lower()` means `"Y"`, `" y"`, and `"y"` all count as the same answer, instead of only an exact match. +Comparing with [`.strip()`](../types/basics.md#modify) and `.lower()` means `"Y"`, `" y"`, and `"y"` all count as the same answer, instead of only an exact match. #### Robust menu @@ -525,7 +525,7 @@ See [Boxes](#boxes) below to wrap the same three options in a decorative border ### Randomize messages -[`random.choice()`](libraries/random.md) picks one item from a list at random, so it prints different messages every run. +[`random.choice()`](../libraries/random.md) picks one item from a list at random, so it prints different messages every run. ```python import random @@ -617,7 +617,7 @@ For anything longer than a line or two, the triple-quoted string is easiest to r ### Formatting variables -F-strings and format specs assemble a formatted string directly, instead of building it up by hand with `+` and manual padding. An [f-string](types.md#building-strings) — a variable's name dropped directly inside `{}` — is what turns the dashboard's bare `snake` dict into a filled-in box. A [format spec](types.md#building-strings) inside that same `{}` controls how the value looks, built from these pieces in order: +F-strings and format specs assemble a formatted string directly, instead of building it up by hand with `+` and manual padding. An [f-string](../types/basics.md#building-strings) — a variable's name dropped directly inside `{}` — is what turns the dashboard's bare `snake` dict into a filled-in box. A [format spec](../types/basics.md#building-strings) inside that same `{}` controls how the value looks, built from these pieces in order: 1. fill (padding character) 2. align (left, right, center, or pad between a sign and its digits) @@ -652,7 +652,7 @@ Box-drawing characters, arrows, and checkmarks give output visual structure that **ASCII** was the original 128 character encoding for computers, standardized in the 1960s — covering English letters, digits, and punctuation on a standard keyboard. Early console styling was built around using these characters to make **ascii text and art**. -Building a [raw string](types.md#building-strings) with an `r` prefix (`r"""..."""`) makes this possible to print - so that Python doesn't mistake the backslashes `\` for meaningful escape characters. +Building a [raw string](../types/basics.md#building-strings) with an `r` prefix (`r"""..."""`) makes this possible to print - so that Python doesn't mistake the backslashes `\` for meaningful escape characters. There are online tools to [convert text to ascii fonts](https://patorjk.com/software/taag/#p=display&f=Isometric1&t=Type+Something+&x=none&v=4&h=4&w=80&we=false) and [find ascii art](https://www.asciiart.eu/#google_vignette). @@ -754,7 +754,7 @@ print(""" ### Progress bars -A pause with no output looks like the program has frozen — printing something that visibly changes during the wait shows it's still working, instead of leaving the screen silent. `time.sleep()` from the [time library](modules.md#import) pauses a program for a set number of seconds. Called in a loop between `print()` calls with [`end=""`](types.md#combine) to keep the cursor on the same line, it fakes a "loading" delay. +A pause with no output looks like the program has frozen — printing something that visibly changes during the wait shows it's still working, instead of leaving the screen silent. `time.sleep()` from the [time library](../resources/modules.md#import) pauses a program for a set number of seconds. Called in a loop between `print()` calls with [`end=""`](../types/basics.md#combine) to keep the cursor on the same line, it fakes a "loading" delay. ```python-ref import time @@ -776,7 +776,7 @@ Loading... Loading... done! ``` -Print with [`end="\r"`](types.md#combine) instead, and each update returns the cursor to the beginning of the same line, allowing the next output to overwrite the previous one and build an animated progress bar out of characters. +Print with [`end="\r"`](../types/basics.md#combine) instead, and each update returns the cursor to the beginning of the same line, allowing the next output to overwrite the previous one and build an animated progress bar out of characters. ```python-ref import time @@ -906,7 +906,7 @@ class a,b,d noborder #### Compatibility -This requires a [terminal](workspace.md#using-the-terminal), either a stand-alone application or inside of an IDE, support varies by which one: +This requires a [terminal](../start/workspace.md#using-the-terminal), either a stand-alone application or inside of an IDE, support varies by which one: === "macOS Terminal" diff --git a/docs/files.md b/docs/resources/files.md similarity index 91% rename from docs/files.md rename to docs/resources/files.md index 58770e7..0ada77c 100644 --- a/docs/files.md +++ b/docs/resources/files.md @@ -224,11 +224,11 @@ with open("notes.txt", "r") as file: | `.read()` / `.readlines()` | O(n) | O(n) | | Loop over the file, line by line | O(n) | O(1) | - `.read()`/`.readlines()` holds the entire file's contents in memory at once (O(n) [space](style.md#time-and-space)). Looping over the file object or calling `.readline()` repeatedly needs only enough memory for the current line, O(1) space regardless of file size. + `.read()`/`.readlines()` holds the entire file's contents in memory at once (O(n) [space](../practices/style.md#time-and-space)). Looping over the file object or calling `.readline()` repeatedly needs only enough memory for the current line, O(1) space regardless of file size. For a small file it doesn't matter; for a file too large to comfortably fit in memory, it's the difference between the program running and it not. - See [Efficiency](style.md#efficiency) for why this distinction matters. + See [Efficiency](../practices/style.md#efficiency) for why this distinction matters. @@ -435,11 +435,11 @@ Everything above is plain text. For other file formats, these Libraries pages bu | Library | Use for | |---|---| -| :material-file-delimited-outline: [csv](libraries/csv.md) | Reading and writing spreadsheets. | -| :material-code-json: [json](libraries/json.md) | Reading and writing JSON data: nested dicts and lists, saved to a file or a string. | -| :material-image-outline: [Pillow](libraries/pillow.md#opening-and-saving-images) | Opening, editing, and saving images, built around one Image object. | -| :material-face-recognition: [OpenCV](libraries/opencv.md#reading-displaying-and-saving-images) | Real-time image and video analysis, built directly on NumPy arrays: color spaces, edge detection, face detection. | -| :material-chart-line: [Matplotlib](libraries/matplotlib.md#saving-a-figure) | Charts and plots: line, bar, and scatter, built directly from plain Python data. | -| :material-application-outline: [tkinter](libraries/tkinter.md#file-dialogs) | Creating desktop applications: text, buttons, dropdowns, forms, output, etc. | +| :material-file-delimited-outline: [csv](../libraries/csv.md) | Reading and writing spreadsheets. | +| :material-code-json: [json](../libraries/json.md) | Reading and writing JSON data: nested dicts and lists, saved to a file or a string. | +| :material-image-outline: [Pillow](../libraries/pillow.md#opening-and-saving-images) | Opening, editing, and saving images, built around one Image object. | +| :material-face-recognition: [OpenCV](../libraries/opencv.md#reading-displaying-and-saving-images) | Real-time image and video analysis, built directly on NumPy arrays: color spaces, edge detection, face detection. | +| :material-chart-line: [Matplotlib](../libraries/matplotlib.md#saving-a-figure) | Charts and plots: line, bar, and scatter, built directly from plain Python data. | +| :material-application-outline: [tkinter](../libraries/tkinter.md#file-dialogs) | Creating desktop applications: text, buttons, dropdowns, forms, output, etc. | diff --git a/docs/modules.md b/docs/resources/modules.md similarity index 96% rename from docs/modules.md rename to docs/resources/modules.md index f8f954c..f2ecb47 100644 --- a/docs/modules.md +++ b/docs/resources/modules.md @@ -30,7 +30,7 @@ flowchart LR -**Library** is the informal umbrella term for either: a single module or a whole package — that's organized to be reused across projects. The [Libraries page](libraries/index.md) highlights a few common published libraries. +**Library** is the informal umbrella term for either: a single module or a whole package — that's organized to be reused across projects. The [Libraries page](../libraries/index.md) highlights a few common published libraries. @@ -196,7 +196,7 @@ import snake_helpers print(snake_helpers.describe("ball")) ``` -Avoid naming your own file after a library you use — your file named `random.py` shadows Python's own `random` module for anything else in that project. See the [file naming rules](workspace.md#step-2-write-and-run-a-python-file) for more. +Avoid naming your own file after a library you use — your file named `random.py` shadows Python's own `random` module for anything else in that project. See the [file naming rules](../start/workspace.md#step-2-write-and-run-a-python-file) for more. ### The main guard diff --git a/docs/foundations.md b/docs/start/foundations.md similarity index 90% rename from docs/foundations.md rename to docs/start/foundations.md index 404ff27..3676b5b 100644 --- a/docs/foundations.md +++ b/docs/start/foundations.md @@ -13,12 +13,12 @@ description: >- - **[Setup](workspace.md) your workspace first** so you can run Python on your computer and edit Python files. - **Work through the pages in order.** - **Type the examples yourself**, and actually click **Run** on the runnable blocks and edit them — change a value, rerun, see what changes. That's where a concept actually sticks, not from reading it. -- **Errors are a normal, constant part of writing code, not a sign you did something wrong.** Once you hit your first one, the [Errors](errors.md) page contains [strategies for resolving them](errors.md#debugging-strategies). - - [Read it out loud](errors.md#read-it-out-loud) - - [Print debugging](errors.md#print-debugging) - - [Isolate the problem](errors.md#isolate-the-problem) -- **Try building something small.** Once you've read through [Conditionals](conditionals.md) and [Loops](loops.md) you already have enough to write a program. -- The homepage has more on [using AI to help you learn](index.md). +- **Errors are a normal, constant part of writing code, not a sign you did something wrong.** Once you hit your first one, the [Errors](../practices/errors.md) page contains [strategies for resolving them](../practices/errors.md#debugging-strategies). + - [Read it out loud](../practices/errors.md#read-it-out-loud) + - [Print debugging](../practices/errors.md#print-debugging) + - [Isolate the problem](../practices/errors.md#isolate-the-problem) +- **Try building something small.** Once you've read through [Conditionals](../flow/conditionals.md) and [Loops](../flow/loops.md) you already have enough to write a program. +- The homepage has more on [using AI to help you learn](../index.md). @@ -76,7 +76,7 @@ Code editors have an **output** window at the bottom that shows the print statem ### Structure of a print() statement { data-card-link="skip" } -`print` is a [function](functions.md) — a named, reusable piece of code that does something when you "call" it by name. These building blocks are all you need to use `print()`: +`print` is a [function](../organization/functions.md) — a named, reusable piece of code that does something when you "call" it by name. These building blocks are all you need to use `print()`:
@@ -233,7 +233,7 @@ species = "burmese python" # replaces the old value entirely a = b = 0 ``` - `species, length_ft = "ball python", 4.5` assigns each value to the matching name in order — the same unpacking mechanism covered on the [Collections](collections.md#packing-and-unpacking) page. `a = b = 0` instead points every name at the *same* value, useful for initializing a few counters at once. + `species, length_ft = "ball python", 4.5` assigns each value to the matching name in order — the same unpacking mechanism covered on the [Collections](../types/collections.md#packing-and-unpacking) page. `a = b = 0` instead points every name at the *same* value, useful for initializing a few counters at once. ??? run "Run a variables example" All the examples above, combined into one script: @@ -298,11 +298,11 @@ print(species, length_ft, "ft") Commas are usually the easier choice for a quick print. Pass `sep="..."` to change the default single-space separator, like `print(species, length_ft, sep=", ")`. -Once you're comfortable with the basics here, the [Collections](collections.md#list-operations) page covers printing the contents of a list or dict. +Once you're comfortable with the basics here, the [Collections](../types/collections.md#list-operations) page covers printing the contents of a list or dict. ### Building a string manually { data-card-link="skip" } -Come back to this once you've read the [Types](types.md) page. +Come back to this once you've read the [Types](../types/basics.md) page. You can also build one string yourself with `+` and print that instead of using commas — but every piece has to already be a string, so a number like `length_ft` needs `str()` first, and you have to add the spaces yourself. @@ -310,7 +310,7 @@ You can also build one string yourself with `+` and print that instead of using print(species + " " + str(length_ft) + " ft") # ball python 4.5 ft — same output, more typing ``` -For building a full sentence out of text and variables, an [f-string](types.md#building-strings) is usually clearer than either approach. +For building a full sentence out of text and variables, an [f-string](../types/basics.md#building-strings) is usually clearer than either approach. ??? run "Run a printing variables example" All the examples above, combined into one script: @@ -332,7 +332,7 @@ For building a full sentence out of text and variables, an [f-string](types.md#b ### Variables and types { data-card-link="skip" } -Come back to this once you've read the [Types](types.md) page. +Come back to this once you've read the [Types](../types/basics.md) page. A variable isn't locked to the type of value it first held — `species` can hold a string, then later be reassigned to an `int` or `float`, with no error. @@ -341,7 +341,7 @@ species = "burmese python" # str species = 12 # now an int — Python allows this ``` -Other languages fix a variable to one type permanently at creation; Python doesn't. Every value still has its own type — [covered in full here](types.md) — a variable is just a name that can point at any of them, one at a time. +Other languages fix a variable to one type permanently at creation; Python doesn't. Every value still has its own type — [covered in full here](../types/basics.md) — a variable is just a name that can point at any of them, one at a time.
@@ -349,7 +349,7 @@ Other languages fix a variable to one type permanently at creation; Python doesn ## Expressions and statements -Every line of Python code is either an **expression** or a **statement**. An expression is anything that evaluates to a value — `2 + 3`, `species`, `species == "burmese"`. A statement is a complete instruction — an assignment, a `print()` call, an `if` statement's condition (covered on the [Conditionals](conditionals.md#if-elif-else) page) — and it's usually built out of one or more expressions. +Every line of Python code is either an **expression** or a **statement**. An expression is anything that evaluates to a value — `2 + 3`, `species`, `species == "burmese"`. A statement is a complete instruction — an assignment, a `print()` call, an `if` statement's condition (covered on the [Conditionals](../flow/conditionals.md#if-elif-else) page) — and it's usually built out of one or more expressions. ```python-ref 2 + 3 # an expression — evaluates to 5 @@ -443,11 +443,11 @@ print("Hello,", name) # now a usable variable ### Converting input to a number { data-card-link="skip" } -Come back to this once you've read the [Types](types.md) page. +Come back to this once you've read the [Types](../types/basics.md) page. Whatever the person types, `input()` always hands it back as a **string** — even a typed number comes back as text, not a real number. -To use what the user has entered as a real number, you must convert it with `int()` or `float()`, covered on the [Types](types.md#convert) page. Skipping this step causes an error the moment you try to do math with it — Python won't add a number to a string. +To use what the user has entered as a real number, you must convert it with `int()` or `float()`, covered on the [Types](../types/basics.md#convert) page. Skipping this step causes an error the moment you try to do math with it — Python won't add a number to a string. ```python-ref age = input("How old are you? ") # "8" — a string, not the number 8 @@ -539,7 +539,7 @@ Python doesn't have a true multi-line comment symbol — a triple-quoted string [^triple-quote-string]: It isn't technically a comment — it's a string that Python creates and then immediately discards since nothing uses it. Python just never complains about a statement that does nothing, so the effect is the same as a real comment. -Placed as the very first line inside a function or a file specifically, this same trick is called a **docstring** and documents what that function or file does. Function docstrings are covered on the [Functions](functions.md#docstrings) page. +Placed as the very first line inside a function or a file specifically, this same trick is called a **docstring** and documents what that function or file does. Function docstrings are covered on the [Functions](../organization/functions.md#docstrings) page. Placed as the very first line of a file instead, it becomes a **module docstring** — documenting the file as a whole rather than a single function, and a common place to note who wrote it and when. @@ -557,7 +557,7 @@ species = "ball python" length_ft = 4.5 ``` -More on docstring conventions on the [Style](style.md#docstrings) page. +More on docstring conventions on the [Style](../practices/style.md#docstrings) page. diff --git a/docs/workspace.md b/docs/start/workspace.md similarity index 96% rename from docs/workspace.md rename to docs/start/workspace.md index 31a77cc..cff8b31 100644 --- a/docs/workspace.md +++ b/docs/start/workspace.md @@ -62,7 +62,7 @@ A **code editor** or an **IDE** ("Integrated Development Environment") is a text - **Running code is easier** — click a Run button from your IDE instead of typing Terminal commands every time - **Code completion** — the editor suggests function names and variables as you type, saving time and reducing typos - **Error detection** — it warns you about common mistakes before you run the code -- **[Debugging](errors.md#debugger-tool)** — pause your code mid-run and inspect variables to track down bugs, instead of only reading output after the fact +- **[Debugging](../practices/errors.md#debugger-tool)** — pause your code mid-run and inspect variables to track down bugs, instead of only reading output after the fact Download one of the **free** code editors below. You can always switch later. @@ -141,7 +141,7 @@ That's it! You've written and run your first Python program. From here, you can ??? tip "Reading error messages" - When you see red error text, the [Errors](errors.md#reading-a-traceback) page covers how to read it. + When you see red error text, the [Errors](../practices/errors.md#reading-a-traceback) page covers how to read it. @@ -234,7 +234,7 @@ It's good for running Python files that are already finished — either your own ## Virtual environments { data-advanced="true" } -Sometimes you'll want to install [external libraries](./libraries/index.md) for your project. A **virtual environment** keeps each project's installed libraries in their own separate folder instead of installing them onto your computer. +Sometimes you'll want to install [external libraries](../libraries/index.md) for your project. A **virtual environment** keeps each project's installed libraries in their own separate folder instead of installing them onto your computer. **Benefits:** diff --git a/docs/types.md b/docs/types/basics.md similarity index 98% rename from docs/types.md rename to docs/types/basics.md index 7edfdd3..c093200 100644 --- a/docs/types.md +++ b/docs/types/basics.md @@ -444,11 +444,11 @@ Strings use the same index and slice syntax as lists. `0` is the first character `.join()` on a list of the same pieces builds the result once, at O(n) — collect the pieces in a list through the loop, then join them after. - See [Efficiency](style.md#efficiency) for why this distinction matters. + See [Efficiency](../practices/style.md#efficiency) for why this distinction matters. -- **`print()`'s `sep` and `end` arguments** also take a string — `sep` replaces the space Python puts between multiple printed values (already covered on [Foundations](foundations.md#print-function)), and `end` replaces the newline `print()` adds after the last one, so the *next* `print()` call continues on the same line instead of starting a new one. +- **`print()`'s `sep` and `end` arguments** also take a string — `sep` replaces the space Python puts between multiple printed values (already covered on [Foundations](../start/foundations.md#print-function)), and `end` replaces the newline `print()` adds after the last one, so the *next* `print()` call continues on the same line instead of starting a new one. ```python-ref print("a", "b", sep="-") # "a-b" @@ -736,7 +736,7 @@ if name: # runs — name isn't empty ## Booleans -A boolean (`bool`) holds one of exactly two values, **`True`** or **`False`** — often used to represent yes/no, on/off, or the result of a comparison. They are used in [if statements](conditionals.md#if-elif-else) and [while loops](loops.md#while-loops). +A boolean (`bool`) holds one of exactly two values, **`True`** or **`False`** — often used to represent yes/no, on/off, or the result of a comparison. They are used in [if statements](../flow/conditionals.md#if-elif-else) and [while loops](../flow/loops.md#while-loops). ```python-ref venomous = False diff --git a/docs/collections.md b/docs/types/collections.md similarity index 93% rename from docs/collections.md rename to docs/types/collections.md index 340c727..5654eb6 100644 --- a/docs/collections.md +++ b/docs/types/collections.md @@ -8,7 +8,7 @@ description: >-
-A **collection** is a single object that groups multiple values (like [basic types](types.md)) together and so they can be stored in one variable together and worked with as a unit. +A **collection** is a single object that groups multiple values (like [basic types](basics.md)) together and so they can be stored in one variable together and worked with as a unit.
@@ -81,7 +81,7 @@ class diagram panel - **Index with `list[index]`** to return the item at that index (position number) of the list. - To **update** the item at that index, set it equal to something else **`list[index] = new_item`**. This works because a list is **mutable** — updating an item changes it in place instead of building a new one, the same way [an object's attributes](classes.md#defining-a-class) can be changed after it's created. + To **update** the item at that index, set it equal to something else **`list[index] = new_item`**. This works because a list is **mutable** — updating an item changes it in place instead of building a new one, the same way [an object's attributes](../organization/classes.md#defining-a-class) can be changed after it's created. *Run the below example, and change the indexes to see how they work:* @@ -126,7 +126,7 @@ class diagram panel species[::2] = ["carpet", "anaconda"] # ["carpet", "rock", "anaconda", "blood"] ``` -### [Loop](loops.md#loop-through-a-collection) through a list +### [Loop](../flow/loops.md#loop-through-a-collection) through a list - Lists make it simple to loop directly over the items. The loop runs once for every item in the list, and on each pass the new loop variable, *(i.e. `specie`)* is set to the next item in the list. @@ -335,7 +335,7 @@ class diagram panel [s.title() for s in species] # ["Burmese", "Rock", "Ball", "Blood"] ``` - Swapping the brackets for parentheses turns this into a [generator expression](functions.md#generator-expressions) instead — same syntax, but it produces items one at a time rather than building the whole list up front. Use a list comprehension when the result needs indexing, `len()`, or looping over more than once; use a generator expression when it's only read once, or the full result would be too large to hold in memory as a list. + Swapping the brackets for parentheses turns this into a [generator expression](../organization/functions.md#generator-expressions) instead — same syntax, but it produces items one at a time rather than building the whole list up front. Use a list comprehension when the result needs indexing, `len()`, or looping over more than once; use a generator expression when it's only read once, or the full result would be too large to hold in memory as a list. ### Going further { data-card-link="skip" } @@ -439,12 +439,12 @@ class diagram panel ??? tip "Extending lists with `collections.deque`" A list can already add or remove items from the end cheaply, but doing the same at the *front* — `species.insert(0, item)` or `species.pop(0)` — means Python has to shift every - other item over. The [`collections`](libraries/collections.md) library's - [`deque`](libraries/collections.md#deque) adds fast `appendleft()`/`popleft()` methods for + other item over. The [`collections`](../libraries/collections.md) library's + [`deque`](../libraries/collections.md#deque) adds fast `appendleft()`/`popleft()` methods for exactly that case. Switch to it when items are being added or removed from both ends often, like a queue of items processed in the order they arrive — not for a list that's mostly read or only changed at the end, where a plain list is simpler and already fast. - See the [collections library page](libraries/collections.md) for the rest of `deque`'s + See the [collections library page](../libraries/collections.md) for the rest of `deque`'s methods (`rotate()`, `maxlen=`, and more) and for the other list-adjacent tools it adds.
@@ -455,9 +455,9 @@ class diagram panel | `append()` / `pop()` | O(1) | O(1) | | `insert(0, x)` / `pop(0)` | O(n) | O(1) | - `append()` and `pop()` (from the end) run in constant [time](style.md#time-and-space) — one step no matter how long the list already is. `insert(0, item)` and `pop(0)` run in linear time, since Python has to shift every remaining item over. + `append()` and `pop()` (from the end) run in constant [time](../practices/style.md#time-and-space) — one step no matter how long the list already is. `insert(0, item)` and `pop(0)` run in linear time, since Python has to shift every remaining item over. - See [Efficiency](style.md#efficiency) for why this distinction matters. + See [Efficiency](../practices/style.md#efficiency) for why this distinction matters. ??? efficiency "For efficiency, sorted() copies the list; sort() doesn't" | | Time | Space | @@ -469,7 +469,7 @@ class diagram panel Reach for `sort()` when the original order doesn't need to survive; `sorted()` when it does. - See [Efficiency](style.md#efficiency) for why this distinction matters. + See [Efficiency](../practices/style.md#efficiency) for why this distinction matters.
@@ -546,9 +546,9 @@ flowchart LR | `if key in snake: snake[key]` | O(1) (two lookups) | — | | `snake.get(key)` | O(1) (one lookup) | — | - `if key in snake: value = snake[key]` does two hash lookups — one to check membership, one to fetch the value. `snake.get(key)` does the same job in one. Both are O(1), so this isn't a [Big O](style.md#big-o-notation) difference, just avoided repeated work — worth reaching for out of habit once it's familiar, not worth restructuring existing code to chase. + `if key in snake: value = snake[key]` does two hash lookups — one to check membership, one to fetch the value. `snake.get(key)` does the same job in one. Both are O(1), so this isn't a [Big O](../practices/style.md#big-o-notation) difference, just avoided repeated work — worth reaching for out of habit once it's familiar, not worth restructuring existing code to chase. - See [Efficiency](style.md#efficiency) for why this distinction matters. + See [Efficiency](../practices/style.md#efficiency) for why this distinction matters.
@@ -713,7 +713,7 @@ flowchart LR Looking up a key with `dict[key]` or `.get()` is O(1) — Python computes where to look directly, the same cost regardless of how many keys the dict holds. Storing the same data as a list of `(key, value)` tuples instead and searching for a match by hand is O(n) — worst case, checking every pair before finding it or coming up empty. That's the main reason to reach for a dict instead of a list when data needs to be looked up by a key. - See [Efficiency](style.md#efficiency) for why this distinction matters. + See [Efficiency](../practices/style.md#efficiency) for why this distinction matters.
@@ -798,22 +798,22 @@ flowchart LR ??? tip "Extending dicts with `collections`" A plain dict can tally counts or group items, but both take extra setup code: checking whether a key exists before incrementing it, or before appending to a list under it. The - [`collections`](libraries/collections.md) library adds several dicts that handle cases + [`collections`](../libraries/collections.md) library adds several dicts that handle cases like these automatically. - - [`Counter`](libraries/collections.md#counter) counts items in a sequence directly — + - [`Counter`](../libraries/collections.md#counter) counts items in a sequence directly — reach for it as soon as a dict's job is "how many times does each item show up." - - [`defaultdict`](libraries/collections.md#defaultdict) supplies an empty value (a list, + - [`defaultdict`](../libraries/collections.md#defaultdict) supplies an empty value (a list, a set, `0`) the first time a new key is used, so grouping items under keys that aren't known ahead of time doesn't need an `if key not in dict` check before every write. - - [`OrderedDict`](libraries/collections.md#ordereddict) is worth reaching for only when + - [`OrderedDict`](../libraries/collections.md#ordereddict) is worth reaching for only when order itself needs to be compared or reordered — a plain dict already remembers insertion order, but its `==` ignores that order, and it has no `move_to_end()`. - - [`ChainMap`](libraries/collections.md#chainmap) layers several dicts together — like a + - [`ChainMap`](../libraries/collections.md#chainmap) layers several dicts together — like a set of overrides checked before a set of defaults — without copying or merging them into a new dict. - See the [collections library page](libraries/collections.md) for the full method list on + See the [collections library page](../libraries/collections.md) for the full method list on each of these. @@ -870,7 +870,7 @@ The **negative index** starts counting down from the end instead, starting at `- ### Loop through a tuple -- The [loop](loops.md#loop-through-a-collection) runs once for every item in the tuple, and on each pass the loop variable, *(i.e. `specie`)* is set to the next item in the tuple. +- The [loop](../flow/loops.md#loop-through-a-collection) runs once for every item in the tuple, and on each pass the loop variable, *(i.e. `specie`)* is set to the next item in the tuple. ```python-ref for specie in species: @@ -943,7 +943,7 @@ The **negative index** starts counting down from the end instead, starting at `- a, b, c, d = species # a="burmese" b="rock" c="ball" d="blood" ``` - A [`match` statement](conditionals.md#unpacking-a-tuple) can do this same unpacking while also branching on the tuple's shape or specific values. + A [`match` statement](../flow/conditionals.md#unpacking-a-tuple) can do this same unpacking while also branching on the tuple's shape or specific values. ```python-ref snake = (12, "ball") @@ -1080,13 +1080,13 @@ The **negative index** starts counting down from the end instead, starting at `- ??? tip "Extending tuples with `collections.namedtuple`" A plain tuple's items can only be accessed by position — `snake[1]` doesn't say what that value actually means without checking back how the tuple was built. The - [`collections`](libraries/collections.md) library's - [`namedtuple`](libraries/collections.md#namedtuple) builds a tuple type with named fields, + [`collections`](../libraries/collections.md) library's + [`namedtuple`](../libraries/collections.md#namedtuple) builds a tuple type with named fields, so the same value reads as `snake.length_ft`. Switch to it once a tuple's positions start needing a mental lookup table to remember, or once several tuples share the same shape throughout a program — a single `namedtuple` definition documents that shape once instead of repeating a comment at every literal. - See the [collections library page](libraries/collections.md) for `namedtuple`'s other + See the [collections library page](../libraries/collections.md) for `namedtuple`'s other methods (`_asdict()`, `_replace()`, default field values) and the rest of the module. @@ -1124,7 +1124,7 @@ block-beta ### Loop through a set -The [loop](loops.md#loop-through-a-collection) runs once for every item in the set, in no guaranteed order, and on each pass the loop variable, *(i.e. `specie`)* is set to the next item. +The [loop](../flow/loops.md#loop-through-a-collection) runs once for every item in the set, in no guaranteed order, and on each pass the loop variable, *(i.e. `specie`)* is set to the next item. ```python-ref for specie in species: @@ -1339,7 +1339,7 @@ These check a relationship between two sets and hand back a `bool`, rather than Checking `in` on a list or tuple is O(n) — worst case, Python has to look at every item before it can say no. A set (and a dict, checking its keys) looks a value up directly instead of scanning, so `in` on either is O(1) on average, regardless of size. That's the "far faster" mentioned above, named precisely — it's also the reason converting a list to a set is a common move before doing a lot of membership checks against it. - See [Efficiency](style.md#efficiency) for why this distinction matters. + See [Efficiency](../practices/style.md#efficiency) for why this distinction matters. diff --git a/mkdocs.yml b/mkdocs.yml index 2c364c1..2049e6c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -9,18 +9,24 @@ hooks: nav: - All: index.md - - Workspace: workspace.md - - Foundations: foundations.md - - Types: types.md - - Collections: collections.md - - Conditionals: conditionals.md - - Loops: loops.md - - Functions: functions.md - - Classes: classes.md - - Modules: modules.md - - Files: files.md - - Style: style.md - - Errors: errors.md + - Start: + - Workspace: start/workspace.md + - Foundations: start/foundations.md + - Types: + - Basics: types/basics.md + - Collections: types/collections.md + - Flow: + - Conditionals: flow/conditionals.md + - Loops: flow/loops.md + - Organization: + - Functions: organization/functions.md + - Classes: organization/classes.md + - Resources: + - Modules: resources/modules.md + - Files: resources/files.md + - Practices: + - Style: practices/style.md + - Errors: practices/errors.md - Libraries: - All: libraries/index.md - Utilities: diff --git a/tests/test_accessibility_browser.py b/tests/test_accessibility_browser.py index 827b892..4c8a7a3 100644 --- a/tests/test_accessibility_browser.py +++ b/tests/test_accessibility_browser.py @@ -20,12 +20,12 @@ # FAQ tabs), a content page with a wide comparison table, a page built from numbered # walkthroughs and tabbed OS instructions, one dense with admonitions, and a library page # full of images. -PAGES = ["/", "/types/", "/workspace/", "/collections/", "/libraries/pillow/"] +PAGES = ["/", "/types/basics/", "/start/workspace/", "/types/collections/", "/libraries/pillow/"] # The pages whose palette does the most work — card grid, wide truth tables — re-checked # with the dark scheme active. (Material lists `slate` first, so dark is already the # default the PAGES run above scans; this forces the *other* direction explicitly too.) -LIGHT_MODE_PAGES = ["/", "/collections/"] +LIGHT_MODE_PAGES = ["/", "/types/collections/"] MOBILE_VIEWPORT = {"width": 375, "height": 812} # Between Material's own tab-bar breakpoint (~1220px) and extra.css's override that pulls @@ -75,7 +75,7 @@ def test_homepage_has_no_axe_violations_in_dark_mode(page, site_url): ) -@pytest.mark.parametrize("path", ["/", "/collections/"]) +@pytest.mark.parametrize("path", ["/", "/types/collections/"]) def test_page_has_no_axe_violations_on_mobile(page, site_url, path): page.set_viewport_size(MOBILE_VIEWPORT) page.goto(f"{site_url}{path}") @@ -89,7 +89,7 @@ def test_page_has_no_axe_violations_on_mobile(page, site_url, path): def test_mobile_nav_drawer_has_no_axe_violations(page, site_url): """The hamburger drawer is a different DOM subtree than the desktop tab nav.""" page.set_viewport_size(MOBILE_VIEWPORT) - page.goto(f"{site_url}/collections/") + page.goto(f"{site_url}/types/collections/") page.evaluate( """() => { const drawer = document.getElementById('__drawer'); @@ -106,7 +106,7 @@ def test_mobile_nav_drawer_has_no_axe_violations(page, site_url): def test_tablet_width_has_no_axe_violations(page, site_url): page.set_viewport_size(TABLET_VIEWPORT) - page.goto(f"{site_url}/types/") + page.goto(f"{site_url}/types/basics/") violations = run_axe(page) assert not violations, ( f"axe-core violations at {TABLET_VIEWPORT['width']}px (custom tab-bar breakpoint):\n" diff --git a/tests/test_accessibility_keyboard.py b/tests/test_accessibility_keyboard.py index 8bc21ac..2935acb 100644 --- a/tests/test_accessibility_keyboard.py +++ b/tests/test_accessibility_keyboard.py @@ -10,7 +10,7 @@ import pytest -CHECK_PAGES = ["/", "/types/", "/workspace/"] +CHECK_PAGES = ["/", "/types/basics/", "/start/workspace/"] @pytest.mark.parametrize("path", CHECK_PAGES) @@ -32,7 +32,7 @@ def test_first_tab_reaches_the_skip_link(page, site_url, path): def test_skip_link_moves_past_the_navigation(page, site_url): - page.goto(f"{site_url}/types/") + page.goto(f"{site_url}/types/basics/") page.keyboard.press("Tab") # focus skip link page.keyboard.press("Enter") # activate it moved = page.evaluate( @@ -95,9 +95,9 @@ def test_palette_toggle_is_keyboard_reachable(page, site_url): # (`.md-button` is only used on 404.md and is already covered statically by # test_accessibility.py's `.md-button:focus-visible` check, so it's not repeated here.) REPO_STYLED_CONTROLS = [ - ("/foundations/", ".md-content a[href]"), - ("/foundations/", ".pyodide-runner__run-btn"), - ("/foundations/", "details.run > summary"), + ("/start/foundations/", ".md-content a[href]"), + ("/start/foundations/", ".pyodide-runner__run-btn"), + ("/start/foundations/", "details.run > summary"), ("/", ".grid.cards a[href]"), ] diff --git a/tests/test_accessibility_runnable.py b/tests/test_accessibility_runnable.py index e3b7418..69ca753 100644 --- a/tests/test_accessibility_runnable.py +++ b/tests/test_accessibility_runnable.py @@ -13,7 +13,7 @@ from conftest import format_violations, run_axe # A core content page that carries several ```python runnable blocks. -RUNNABLE_PAGE = "/foundations/" +RUNNABLE_PAGE = "/start/foundations/" def _open(page, site_url): diff --git a/tests/test_essentials_toggle.py b/tests/test_essentials_toggle.py index 9752dd6..e1fde94 100644 --- a/tests/test_essentials_toggle.py +++ b/tests/test_essentials_toggle.py @@ -45,7 +45,7 @@ def test_simplified_state_carries_to_content_page_heading_and_toc(page, site_url """functions.md's own '## Decorators { data-advanced="true" }' heading (and its integrated-TOC entry) should hide too — carried over from the homepage's marker via localStorage, with no need to visit the homepage first in this same test.""" - page.goto(f"{site_url}/functions/?simplified=true") + page.goto(f"{site_url}/organization/functions/?simplified=true") result = page.evaluate( """() => { @@ -68,7 +68,7 @@ def test_admonition_inside_a_hidden_section_is_actually_hidden(page, site_url): on an admonition inside a hidden section didn't actually hide it — it stayed on screen as a bordered box even though its heading was gone. Fixed with a blanket `[hidden] { display: none !important }` in extra.css.""" - page.goto(f"{site_url}/collections/?simplified=true") + page.goto(f"{site_url}/types/collections/?simplified=true") hidden_and_shown = page.evaluate( """() => [...document.querySelectorAll('.md-typeset details')] @@ -88,7 +88,7 @@ def test_pfg_section_wrapper_is_hidden_with_its_heading(page, site_url): heading and its flow siblings, which sit *inside* that wrapper — the wrapper itself was never touched, so it stayed on screen as an empty bordered card once everything inside it was hidden.""" - page.goto(f"{site_url}/collections/?simplified=true") + page.goto(f"{site_url}/types/collections/?simplified=true") result = page.evaluate( """() => { @@ -111,7 +111,7 @@ def test_link_to_hidden_section_recovers_to_advanced(page, site_url): while the "Tuples" heading itself is hidden by data-advanced — clicking that visible link should flip the toggle back to Advanced and reveal the section, rather than landing on a hidden target and doing nothing.""" - page.goto(f"{site_url}/collections/?simplified=true") + page.goto(f"{site_url}/types/collections/?simplified=true") tuples_link = page.locator('table a[href$="#tuples"]') assert tuples_link.count() > 0, "expected the cheat-sheet table's #tuples link to exist" @@ -144,9 +144,13 @@ def test_link_to_hidden_section_recovers_to_advanced(page, site_url): def test_link_recovery_ignores_toc_links_to_visible_sections(page, site_url): """Sanity check the recovery handler isn't overly broad: clicking an ordinary link to a section that's already visible shouldn't touch Simplify state at all.""" - page.goto(f"{site_url}/collections/?simplified=true") + page.goto(f"{site_url}/types/collections/?simplified=true") - lists_link = page.locator('a.md-nav__link[href$="#lists"]') + # Material renders a page's TOC twice: once for real in the secondary + # (right-hand) sidebar, and once inert (visibility:collapse) inside the + # primary nav's copy of the current page's entry — scope to the visible + # one so .first doesn't land on the inert copy and time out. + lists_link = page.locator('.md-sidebar--secondary a.md-nav__link[href$="#lists"]') assert lists_link.count() > 0 lists_link.first.click() diff --git a/tests/test_structure.py b/tests/test_structure.py index ba470ea..38c1ef1 100644 --- a/tests/test_structure.py +++ b/tests/test_structure.py @@ -196,7 +196,7 @@ def test_python_is_capitalized_in_prose(path): # pages that are the clearest, most literal examples of the documented convention. Extending it # to more pages would need either a markup convention to mark "this block follows the cheat # sheet rule" or a per-page editorial pass — worth doing, but a separate task from this suite. -CHECKED_PAGES_FOR_PYTHON_REF_COMMENTS = {"types.md", "collections.md"} +CHECKED_PAGES_FOR_PYTHON_REF_COMMENTS = {"types/basics.md", "types/collections.md"} _CONTROL_FLOW_RE = re.compile( r"^(if |elif |else|while |for |def |class |try|except|finally|with |return|import |from |@|match |case )" From e7785854d73e36f9c9c16f4817b546a5f2223021 Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Thu, 24 Sep 2026 22:00:31 -0700 Subject: [PATCH 2/7] integrate mkdocs-nested-tabs plugin --- README.md | 24 +++++++++++++ docs/javascripts/essentials_toggle.js | 7 ++-- docs/stylesheets/extra.css | 52 +++++++++++++++++++++++++++ mkdocs.yml | 10 ++++-- requirements.txt | 3 ++ 5 files changed, 92 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5f5e8b2..8d6200c 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ - [Content](#content) - [Site generator](#site-generator) - [Client-side rendering](#client-side-rendering) +- [New open source](#new-open-source) - [Theme](#theme) - [Content conventions](#content-conventions) - [Running locally](#running-locally) @@ -133,6 +134,29 @@ Some examples of content that is hidden while in "Essentials" mode, while a stud - Workspace/tooling topics as most students are using an IDE (using the terminal, virtual environments) - Efficiency, awareness of space and time resources, Big O notation +## New open source + +### [mkdocs-nested-tabs](https://pypi.org/project/mkdocs-nested-tabs/) + +I published a new mkdocs plugin to add functionality I wanted for this site. + +A multi-level two row header — every top-level category shown with all of its child pages +listed underneath. An enhancement to Material's native tabs (which only reveal a category's children via a hover dropdown, one at a time) — started as site-specific JavaScript here, then got extracted into its own published PyPi plugin. + +```bash +pip install mkdocs-nested-tabs +``` + +```yaml +theme: + features: + - navigation.tabs +plugins: + - nested-tabs +``` + +I extracted it because it fills a real, previously-requested gap — someone asked for exactly this in a [Material for MkDocs discussion](https://github.com/squidfunk/mkdocs-material/discussions/4765) and the maintainer's answer was horizontal scroll, not an expanded layout — and nothing on PyPI already does it (checked against the existing nav/dropdown/sidebar plugins first). It reads a site's `nav:` tree directly at runtime, so it needs no plugin-specific configuration for the common case, and falls back to Material's own theme variables for styling so it looks reasonable on any palette out of the box. This site is its first real consumer — see `mkdocs.yml`'s `plugins:` list and `extra.css`'s `--md-nested-tabs-*` overrides for how it's wired in here. + ## Theme ### Custom CSS diff --git a/docs/javascripts/essentials_toggle.js b/docs/javascripts/essentials_toggle.js index 561d58d..35b7684 100644 --- a/docs/javascripts/essentials_toggle.js +++ b/docs/javascripts/essentials_toggle.js @@ -45,8 +45,11 @@ } } - // toc.integrate puts headings in the same nav as site links — hide the - // matching
  • too, so there's no dead link to hidden content. + // Hide the matching TOC
  • too, so there's no dead link to hidden + // content. Material renders a heading's link twice — once (inert, + // visibility:collapse) inside the primary nav's copy of the current + // page's TOC, and once for real in the secondary sidebar — querySelectorAll + // + forEach covers both without needing to know which is which. function setTocEntryHidden(id, hidden) { // href gets rewritten to a full URL after hydration; match by suffix. document.querySelectorAll('a.md-nav__link[href$="#' + id + '"]').forEach(function (link) { diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 1d8ac4e..a3ba410 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -53,6 +53,11 @@ --pt-shimmer-gold: #D9C48C; --pt-shimmer-sage: #B9CDAE; --pt-shimmer-clay: #CBA989; + + /* Placeholder light green for the active top-nav tab (e.g. "Flow" when + conditionals.md is open), just to give it a distinct color from the + default text color and the visited-link accent. Pick a deliberate + final value later. */ } [data-md-color-scheme="slate"] { @@ -87,6 +92,8 @@ --pt-shimmer-indigo: #201f3d; --pt-shimmer-purple: #2a1c3d; --pt-shimmer-green: #17281f; + + --pt-nav-active: #8FCB93; } /* Material re-sets --md-* variables on [data-md-color-scheme] (applied to @@ -115,6 +122,27 @@ --md-typeset-a-color: var(--pt-accent); + /* mkdocs-nested-tabs override hooks (see CLAUDE.md's "Planned extraction" + section). The page-link row keeps the standard --pt-ink default (dark + ink in light mode, off-white in dark mode) used everywhere else for + default header/tab text. The all-caps category-label row above it uses + --pt-text-secondary instead — a lower-contrast grey, lighter than full + ink in light mode and darker than full white in dark mode — so the two + rows read as visually distinct tiers rather than identical text. + + --md-nested-tabs-label-active-color (added in mkdocs-nested-tabs 0.2.0) + colors the category label itself when one of its own pages is active — + e.g. "Flow" going --pt-nav-active while "Conditionals" is open. The + active page link keeps the plugin's default (--md-accent-fg-color, i.e. + --pt-accent) rather than a --md-nested-tabs-link-active-color override, + so parent and child read as two distinct active tones, not one. Before + 0.2.0 added this hook, achieving the same thing needed a :has() rule + reaching into the plugin's markup from outside — see git history if + that's ever relevant again. */ + --md-nested-tabs-label-color: var(--pt-text-secondary); + --md-nested-tabs-link-color: var(--pt-ink); + --md-nested-tabs-label-active-color: var(--pt-nav-active); + --md-code-bg-color: var(--pt-panel); --md-code-fg-color: var(--pt-ink); @@ -172,9 +200,15 @@ html:focus-within::-webkit-scrollbar-thumb { color: var(--pt-ink); } +/* Soft halo in --pt-panel (a lighter, in-palette tint of --pt-bg rather than + an arbitrary white) so the title reads clearly over the busier parts of + the header's illustrated background, without needing a solid pill/box + behind the text. Two stacked passes build up more coverage than a single + blur radius gives on its own. */ .md-header__title, .md-header__topic { font-weight: 800; + text-shadow: 0 0 3px var(--pt-panel), 0 0 6px var(--pt-panel); } /* Below ~45em the header's own title font-size (Material's default) plus @@ -701,6 +735,24 @@ input:checked + .md-consent__settings { } } +/* The header nav row (now the mkdocs-nested-tabs plugin's own .nested-tabs + — see CLAUDE.md's "Planned extraction" section) covers category/page + navigation on desktop, so the left nav sidebar (site tree: categories + + sibling pages) is redundant there — hide it above the same breakpoint the + plugin uses. mkdocs.yml no longer sets toc.integrate, so this sidebar only + ever held the site tree, never the current page's own heading TOC — that + renders separately as the standard right-hand .md-sidebar--secondary + column (unaffected by this rule), same as it did before the site's earlier + migration to toc.integrate (see the nav history notes in CLAUDE.md). + Desktop-only and guarded by min-width on purpose: .md-sidebar--primary is + the exact same DOM element Material reuses as the off-canvas mobile + hamburger drawer, so an unscoped display:none here would silently break + mobile nav entirely (this happened once already). */ +@media screen and (min-width: 76.234375em) { + .md-sidebar--primary { + display: none; + } +} /* :visited alone (0,2,1) outranks .md-button's two plain classes (0,2,0), so a visited link would otherwise fall through to the ".md-typeset a:visited" accent-on-accent color above and vanish into the button's own diff --git a/mkdocs.yml b/mkdocs.yml index 2049e6c..10250e5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -7,6 +7,13 @@ site_description: >- hooks: - hooks/thanks_url.py +plugins: + # Setting `plugins:` at all replaces MkDocs' implicit default list, which + # otherwise includes `search` automatically — has to be listed explicitly + # here or the site silently loses its search feature. + - search + - nested-tabs + nav: - All: index.md - Start: @@ -79,15 +86,14 @@ theme: icon: material/weather-night name: Switch to dark mode features: - - navigation.expand - navigation.footer - navigation.instant - navigation.instant.prefetch + - navigation.sections - navigation.tabs - navigation.tabs.sticky - navigation.top - navigation.tracking - - toc.integrate - toc.follow - content.code.copy - content.tooltips diff --git a/requirements.txt b/requirements.txt index 0b6b41c..72a82d9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,8 @@ mkdocs mkdocs-material +# Extracted nested-tabs header row — see CLAUDE.md's "Planned extraction" +# section. Published to PyPI; was a local editable install before 2026-09-23. +mkdocs-nested-tabs==0.2.0 pytest playwright pytest-playwright From 60904eb7f00e7030c5fcf30a8eeee72b4160e229 Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Thu, 24 Sep 2026 22:02:54 -0700 Subject: [PATCH 3/7] cheatsheet button --- docs/javascripts/cheatsheet_button.js | 41 +++++++++++ docs/stylesheets/extra.css | 99 ++++++++++++++++++++++++++- mkdocs.yml | 1 + 3 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 docs/javascripts/cheatsheet_button.js diff --git a/docs/javascripts/cheatsheet_button.js b/docs/javascripts/cheatsheet_button.js new file mode 100644 index 0000000..29e40c1 --- /dev/null +++ b/docs/javascripts/cheatsheet_button.js @@ -0,0 +1,41 @@ +(function () { + // Explicit "Cheatsheet" text shortcut to the homepage (already this + // site's compact quick-reference dashboard — see README), replacing the + // logo image in the header's top-left slot rather than sitting next to + // it — the logo already linked home with no other purpose, so the pill + // takes over that exact role instead of duplicating it. Styled in + // extra.css to match the Essentials/Advanced toggle's own active/ + // inactive look. The logo itself is hidden via CSS (.md-header__button + // .md-logo { display: none }), not removed here, so this script only + // owns inserting the pill. + function render() { + const existing = document.querySelector(".pt-cheatsheet-link"); + if (existing) existing.remove(); + + const title = document.querySelector(".md-header__title"); + const logo = document.querySelector(".md-header__button.md-logo"); + if (!title || !logo) return; + + const link = document.createElement("a"); + link.className = "pt-cheatsheet-link"; + link.href = logo.getAttribute("href"); + link.textContent = "Cheatsheet"; + + // Same homepage check as homepage_header_title.js — kept independent + // rather than shared, since one more `===` comparison isn't worth a + // cross-file dependency between two otherwise-unrelated scripts. + const isHomepage = window.location.pathname.replace(/index\.html$/, "") === "/"; + if (isHomepage) { + link.classList.add("pt-cheatsheet-link--active"); + link.setAttribute("aria-current", "page"); + } + + title.insertAdjacentElement("beforebegin", link); + } + + if (window.document$) { + window.document$.subscribe(render); + } else { + document.addEventListener("DOMContentLoaded", render); + } +})(); diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index a3ba410..aed711e 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -58,6 +58,7 @@ conditionals.md is open), just to give it a distinct color from the default text color and the visited-link accent. Pick a deliberate final value later. */ + --pt-nav-active: #6FA36B; } [data-md-color-scheme="slate"] { @@ -345,8 +346,89 @@ html:focus-within::-webkit-scrollbar-thumb { 100% { background-position: 0% 50%; } } -.md-header__button.md-logo img { - border: 1px solid rgba(103, 58, 183, 0.6); +/* The logo only ever linked home with no other purpose, so + cheatsheet_button.js's "Cheatsheet" pill replaces it in the header's + top-left slot instead of sitting alongside it — hidden here, not + removed from the DOM, since Material still generates it normally. */ +.md-header__button.md-logo { + display: none; +} + +/* "Cheatsheet" link injected by cheatsheet_button.js, in the logo's old + spot (leading the site title) — an explicit text shortcut to the + homepage (already this site's compact quick-reference dashboard) rather + than relying on the logo image alone reading as clickable. Deliberately + reuses the Essentials/Advanced toggle's own technique (a currentColor + pill fill + --pt-bg text flip when active) rather than hardcoded colors, + so it adapts across light/dark mode exactly the way that toggle does — + currentColor here is .md-header's own `color: var(--pt-ink)`, inherited, + not set locally. */ +.pt-cheatsheet-link { + display: inline-flex; + align-items: center; + margin-right: 0.6rem; + padding: 0.15rem 0.6rem; + /* Matches .pt-simplify-toggle's own resting track (border + background) + below, so the inactive pill reads as the same kind of control rather + than plain text — var(--pt-ink) directly instead of currentColor, + sidestepping the self-reference risk noted below for the active state. */ + border: 0.05rem solid var(--pt-ink); + border-radius: 1rem; + background-color: var(--pt-bg); + font-size: 0.6rem; + font-weight: 700; + letter-spacing: 0.02em; + color: currentColor; + text-decoration: none; + white-space: nowrap; +} + +/* Material Symbols "dashboard" (viewBox 0 -960 960 960), same mask-image + technique as .pt-simplify-option's icons above — an asymmetric mixed-size + panel shape, matching the homepage's own grid (its library boxes are + genuinely different widths, not a uniform grid — see the "External + files and resources" grid-math notes in CLAUDE.md) rather than a plain + symmetric grid icon. Fetched from Google's material-design-icons source + directly (not hand-drawn) to keep the path data exact. */ +.pt-cheatsheet-link::after { + content: ""; + display: inline-block; + width: 0.7rem; + height: 0.7rem; + flex: none; + margin-left: 0.3rem; + background-color: currentColor; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-size: contain; + mask-size: contain; + -webkit-mask-position: center; + mask-position: center; + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -960 960 960'%3E%3Cpath d='M520-600v-240h320v240H520ZM120-440v-400h320v400H120Zm400 320v-400h320v400H520Zm-400 0v-240h320v240H120Zm80-400h160v-240H200v240Zm400 320h160v-240H600v240Zm0-480h160v-80H600v80ZM200-200h160v-80H200v80Zm160-320Zm240-160Zm0 240ZM360-280Z'/%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -960 960 960'%3E%3Cpath d='M520-600v-240h320v240H520ZM120-440v-400h320v400H120Zm400 320v-400h320v400H520Zm-400 0v-240h320v240H120Zm80-400h160v-240H200v240Zm400 320h160v-240H600v240Zm0-480h160v-80H600v80ZM200-200h160v-80H200v80Zm160-320Zm240-160Zm0 240ZM360-280Z'/%3E%3C/svg%3E"); +} + +.pt-cheatsheet-link:hover, +.pt-cheatsheet-link:focus-visible { + text-decoration: underline; +} + +/* Not background-color: currentColor here — the toggle's pill and its text + are two separate elements, so its highlight span can safely use + currentColor (the *inherited* color) while a different element's text + flips to --pt-bg. Collapsed into one element like this, currentColor + would resolve against this same rule's own `color: var(--pt-bg)` below + it (CSS uses the final computed color, not declaration order) — pill and + text would end up identical, text invisible. --pt-ink directly sidesteps + that self-reference entirely. */ +.pt-cheatsheet-link--active { + background-color: var(--pt-ink); + color: var(--pt-bg); +} + +.pt-cheatsheet-link--active:hover, +.pt-cheatsheet-link--active:focus-visible { + text-decoration: none; } .md-tabs { @@ -360,7 +442,18 @@ html:focus-within::-webkit-scrollbar-thumb { is no .md-tabs__link--active class. */ .md-tabs__item--active .md-tabs__link { font-weight: 700; -} + color: var(--pt-nav-active); +} + +/* The desktop version of this same active-parent color (above the + mkdocs-nested-tabs plugin's 76.234375em breakpoint, where .md-tabs is + hidden and replaced by that plugin's own .nested-tabs row — a separate + DOM tree the rule above never reaches) doesn't need a rule here at all: + --md-nested-tabs-label-active-color, set above, is a first-class hook the + plugin's own CSS applies via .nested-tabs__label--active as of 0.2.0. + Before that version, the plugin never marked a flat category's own label + active (only the child page link), so this used to need a :has() selector + reaching into the plugin's markup from outside — see git history. */ /* Material redefines --md-typeset-a-color to --md-primary-fg-color somewhere deeper in its own cascade (our light-cream header color), which makes body diff --git a/mkdocs.yml b/mkdocs.yml index 10250e5..be0409c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -155,6 +155,7 @@ extra_javascript: - javascripts/copyright_year.js - javascripts/external_links.js - javascripts/homepage_header_title.js + - javascripts/cheatsheet_button.js - javascripts/essentials_toggle.js - javascripts/a11y_patches.js - https://unpkg.com/mermaid@11/dist/mermaid.min.js From 730b84916be7009811d4eeaf39f7a6c777fa3f2c Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Thu, 24 Sep 2026 22:03:13 -0700 Subject: [PATCH 4/7] custom header image for mobile --- docs/stylesheets/extra.css | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index aed711e..7531080 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -333,6 +333,34 @@ html:focus-within::-webkit-scrollbar-thumb { mask-repeat: repeat-x; } +/* Mobile gets its own header art (same background-strip + mask setup, just + a different source file per scheme) rather than scaling the desktop + artwork down — a wide illustration loses detail and can read as visual + noise at the header's much shorter mobile height. Same 45em breakpoint as + the title-sizing rule above, so the art and the title layout switch at the + same width. Swapping just the url() here (not duplicating the whole rule + block) keeps the background-size/position/repeat and the mask wiring + defined once. */ +@media (max-width: 45em) { + [data-md-color-scheme="slate"] .md-header { + background-image: url("../img/header_dark_mobile.svg"); + } + + [data-md-color-scheme="slate"] .md-header::before { + -webkit-mask-image: url("../img/header_dark_mobile.svg"); + mask-image: url("../img/header_dark_mobile.svg"); + } + + [data-md-color-scheme="default"] .md-header { + background-image: url("../img/header_light_mobile.svg"); + } + + [data-md-color-scheme="default"] .md-header::before { + -webkit-mask-image: url("../img/header_light_mobile.svg"); + mask-image: url("../img/header_light_mobile.svg"); + } +} + @media (prefers-reduced-motion: no-preference) { [data-md-color-scheme="slate"] .md-header::before, [data-md-color-scheme="default"] .md-header::before { From 5eed9b096bbc112a690ce25666169b5ba360d80d Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Fri, 25 Sep 2026 09:03:04 -0700 Subject: [PATCH 5/7] toast styling --- docs/javascripts/essentials_toggle.js | 2 +- docs/stylesheets/extra.css | 80 ++++++++++++++++----------- 2 files changed, 49 insertions(+), 33 deletions(-) diff --git a/docs/javascripts/essentials_toggle.js b/docs/javascripts/essentials_toggle.js index 35b7684..4d39b4f 100644 --- a/docs/javascripts/essentials_toggle.js +++ b/docs/javascripts/essentials_toggle.js @@ -102,7 +102,7 @@ icon.dataset[key] = iconAttrs[key]; }); icon.setAttribute("aria-hidden", "true"); - title.append(label + " ", icon); + title.append(icon, " " + label); toast.append(title); if (body) { diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 7531080..160afd7 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -417,14 +417,16 @@ html:focus-within::-webkit-scrollbar-thumb { genuinely different widths, not a uniform grid — see the "External files and resources" grid-math notes in CLAUDE.md) rather than a plain symmetric grid icon. Fetched from Google's material-design-icons source - directly (not hand-drawn) to keep the path data exact. */ -.pt-cheatsheet-link::after { + directly (not hand-drawn) to keep the path data exact. Leads the word + (::before, not ::after), matching the icon-then-text order used by the + Essentials/Advanced toggle, the theme toggle, and the toast. */ +.pt-cheatsheet-link::before { content: ""; display: inline-block; width: 0.7rem; height: 0.7rem; flex: none; - margin-left: 0.3rem; + margin-right: 0.3rem; background-color: currentColor; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; @@ -1080,18 +1082,24 @@ input:checked + .md-consent__settings { /* Icon for each option (Material Symbols "psychiatry" / "park", viewBox 0 -960 960 960), same mask-image technique as .pt-theme-option's - sun/moon. On desktop it trails the "Essentials"/"Advanced" word — an - ::after, not ::before, so it renders after the label text in normal - document order without any extra markup or flex re-ordering; the - option button itself is a flex row (see .pt-simplify-option above), so + sun/moon. On desktop it leads the "Essentials"/"Advanced" word — a + ::before, same as .pt-theme-option's own icon and the toast's + .pt-mode-icon (see below), so all three read consistently as + "icon, then text" — matching Material's own "back to top" button + (`.md-top`: icon SVG, then the "Back to top" label). The option + button itself is a flex row (see .pt-simplify-option above), so the icon centers vertically against the text the same way .pt-theme- option's icon-only button centers its own icon — no vertical-align fudging needed, and it can't drift out of sync between the two variants. Below ~45em (matches this file's other mobile breakpoints) the words get tight, so the label is visually clipped and the icon alone represents the option — it stays in the DOM (not aria-hidden) so the - button's accessible name is unchanged for screen readers either way. */ -.pt-simplify-option[data-mode]::after, + button's accessible name is unchanged for screen readers either way. + Sized smaller (0.7rem) than the toast's own .pt-mode-icon (1.2rem, + matching Material's "back to top" icon) — this icon sits inside the + 1.2rem-tall toggle track itself, so it stays the toggle's own icon + size rather than the toast's. */ +.pt-simplify-option[data-mode]::before, .pt-mode-icon { content: ""; display: inline-block; @@ -1107,25 +1115,32 @@ input:checked + .md-consent__settings { mask-position: center; } -.pt-simplify-option[data-mode]::after { - margin-left: 0.3rem; +.pt-simplify-option[data-mode]::before { + margin-right: 0.3rem; } -.pt-simplify-option[data-mode="simplified"]::after, +/* Toast-only override: matches the size of Material's own "back to top" + icon (`.md-icon svg`, 1.2rem), per the shared rule's comment above. */ +.pt-mode-icon { + width: 1.2rem; + height: 1.2rem; +} + +.pt-simplify-option[data-mode="simplified"]::before, .pt-mode-icon[data-mode="simplified"] { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -960 960 960'%3E%3Cpath d='M450-130v-309h-20q-64 0-120.5-24.5T209-533q-44-45-66.5-104T120-760v-80h78.32Q260-840 317-815.5 374-791 419-746q33 34 54.5 76t30.5 89q7.65-11.9 16.82-22.95Q530-615 540-626q45-45 102-69.5T761.67-720H840v80q0 64-23.98 123T748-413q-45 45-101.56 69T528-320h-18v190h-60Zm1-370q0-61-20-113.5t-55-89q-35-36.5-86-57T180-780q0 63 18.5 115.5T252-575q42 45 90.5 60T451-500Zm59 120q60 0 111-19.5t86-56q35-36.5 54-89T780-660q-60 0-111 20.5T583-583q-43 45-58 94t-15 109Zm0 0Zm-59-120Z'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -960 960 960'%3E%3Cpath d='M450-130v-309h-20q-64 0-120.5-24.5T209-533q-44-45-66.5-104T120-760v-80h78.32Q260-840 317-815.5 374-791 419-746q33 34 54.5 76t30.5 89q7.65-11.9 16.82-22.95Q530-615 540-626q45-45 102-69.5T761.67-720H840v80q0 64-23.98 123T748-413q-45 45-101.56 69T528-320h-18v190h-60Zm1-370q0-61-20-113.5t-55-89q-35-36.5-86-57T180-780q0 63 18.5 115.5T252-575q42 45 90.5 60T451-500Zm59 120q60 0 111-19.5t86-56q35-36.5 54-89T780-660q-60 0-111 20.5T583-583q-43 45-58 94t-15 109Zm0 0Zm-59-120Z'/%3E%3C/svg%3E"); } -.pt-simplify-option[data-mode="advanced"]::after, +.pt-simplify-option[data-mode="advanced"]::before, .pt-mode-icon[data-mode="advanced"] { -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -960 960 960'%3E%3Cpath d='M538-80H423v-149H120l189-274h-95l266-377 266 377h-94l188 274H538v149ZM236-289h189-90 290-89 189-489Zm0 0h489L536-563h89L480-769 335-563h90L236-289Z'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -960 960 960'%3E%3Cpath d='M538-80H423v-149H120l189-274h-95l266-377 266 377h-94l188 274H538v149ZM236-289h189-90 290-89 189-489Zm0 0h489L536-563h89L480-769 335-563h90L236-289Z'/%3E%3C/svg%3E"); } @media (max-width: 45em) { - .pt-simplify-option[data-mode]::after { - margin-left: 0; + .pt-simplify-option[data-mode]::before { + margin-right: 0; } .pt-simplify-toggle:has(.pt-simplify-option[data-mode]) .pt-simplify-option { @@ -1206,31 +1221,32 @@ input:checked + .md-consent__settings { mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -960 960 960'%3E%3Cpath d='M480-120q-150 0-255-105T120-480q0-150 105-255t255-105q8 0 17 .5t23 1.5q-36 32-56 79t-20 99q0 90 63 153t153 63q52 0 99-18.5t79-51.5q1 12 1.5 19.5t.5 14.5q0 150-105 255T480-120Zm0-60q109 0 190-67.5T771-406q-25 11-53.67 16.5Q688.67-384 660-384q-114.69 0-195.34-80.66Q384-545.31 384-660q0-24 5-51.5t18-62.5q-98 27-162.5 109.5T180-480q0 125 87.5 212.5T480-180Zm-4-297Z'/%3E%3C/svg%3E"); } -/* Disappearing confirmation toast for the Essentials/Advanced toggle - (docs/javascripts/essentials_toggle.js's showToast). Sits just below the - header — right where the toggle that triggered it lives — rather than - at the bottom of the screen, so it's immediately next to the control the - reader just clicked. `top` itself is set inline by showToast() (the - header's own height varies: ~48px on mobile, taller on desktop/tablet - with the tab bar, and Material can also hide/reveal it on scroll), only - the offset-from-header gap lives here. Centered horizontally on both - desktop and mobile. Colors are inverted (ink background, bg-colored - text) same as the toggle's own active-option highlight, so it reads as - "this site's accent chip," not a generic OS notification. */ +/* Disappearing confirmation toast for the Essentials/Advanced and + light/dark toggles (docs/javascripts/essentials_toggle.js's showToast). + Sits just below the header — right where the toggle that triggered it + lives — rather than at the bottom of the screen, so it's immediately + next to the control the reader just clicked. `top` itself is set inline + by showToast() (the header's own height varies: ~48px on mobile, taller + on desktop/tablet with the tab bar, and Material can also hide/reveal it + on scroll), only the offset-from-header gap lives here. Centered + horizontally on both desktop and mobile. Shape/colors match Material's + own "back to top" pill (`.md-top`) — same rounded-pill radius, shadow, + bg/fg pairing, font size, and padding — so it reads as a native site + chrome element rather than a one-off custom popup. */ .pt-toast { position: fixed; z-index: 1000; left: 50%; transform: translate(-50%, 0); max-width: min(90vw, 22rem); - padding: 0.6rem 1rem; - border-radius: 0.4rem; - background-color: var(--pt-ink); - color: var(--pt-bg); - font-size: 0.75rem; + padding: 0.4rem 0.8rem; + border-radius: 1.6rem; + background-color: var(--md-default-bg-color); + color: var(--md-default-fg-color--light); + font-size: 0.7rem; line-height: 1.4; text-align: center; - box-shadow: 0 0.2rem 0.6rem rgba(0, 0, 0, 0.25); + box-shadow: var(--md-shadow-z2); opacity: 0; /* none while hidden, so an invisible toast (opacity alone doesn't take it out of hit-testing) doesn't block clicks on whatever's under where From f73fb4e6f6fabd8c66a043abbed0334a373a1786 Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Fri, 25 Sep 2026 09:09:57 -0700 Subject: [PATCH 6/7] padding above nav --- docs/stylesheets/extra.css | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 160afd7..9a85f3c 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -463,6 +463,9 @@ html:focus-within::-webkit-scrollbar-thumb { .md-tabs { background-color: transparent; + /* Extra breathing room above the tab row itself, beyond Material's + default (the header title sits right above it otherwise). */ + padding-top: 0.5rem; } /* Make the current page's top-nav tab visually heavier than the rest, so @@ -858,6 +861,18 @@ input:checked + .md-consent__settings { } } +/* Same extra top breathing room as .md-tabs above, applied to the + mkdocs-nested-tabs plugin's own row (nested-tabs.css sets + .nested-tabs__list's padding to 0.5rem 0 — override padding-top only, so + the bottom padding it sets stays as-is). extra.css loads after + nested-tabs.css (see mkdocs.yml's extra_css / plugin injection order), + so this wins without needing extra specificity. */ +@media screen and (min-width: 76.234375em) { + .nested-tabs__list { + padding-top: 1.3rem; + } +} + /* The header nav row (now the mkdocs-nested-tabs plugin's own .nested-tabs — see CLAUDE.md's "Planned extraction" section) covers category/page navigation on desktop, so the left nav sidebar (site tree: categories + From cb3c82c480b3d1f12534b06edd882d67d7e671fa Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Fri, 25 Sep 2026 11:14:02 -0700 Subject: [PATCH 7/7] tab styling --- docs/stylesheets/extra.css | 43 +++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 9a85f3c..275317b 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -54,11 +54,11 @@ --pt-shimmer-sage: #B9CDAE; --pt-shimmer-clay: #CBA989; - /* Placeholder light green for the active top-nav tab (e.g. "Flow" when - conditionals.md is open), just to give it a distinct color from the - default text color and the visited-link accent. Pick a deliberate - final value later. */ - --pt-nav-active: #6FA36B; + /* Active top-nav tab (e.g. "Flow" when conditionals.md is open) — the + site's own accent green, deliberately darker than the inactive tabs' + --pt-text-secondary below it, so the current page reads as the + "heavier," more deliberate mark rather than a lighter highlight. */ + --pt-nav-active: var(--pt-accent); } [data-md-color-scheme="slate"] { @@ -272,6 +272,25 @@ html:focus-within::-webkit-scrollbar-thumb { background-repeat: repeat-x; } +/* Material's own collapsed/idle search box (shown ≥60em before it's + clicked into) defaults to a mostly-transparent black fill + (`background-color: #00000042`, ~26% opaque, meant to sit on Material's + flat primary-color header). Against this site's much darker slate + header — plus the shimmer artwork underneath it — that low opacity + barely reads as a distinct control. Raise the opacity so the search box + is actually visible as its own element; once expanded/active it already + switches to a solid `var(--md-default-bg-color)` fill via Material's own + `[data-md-toggle=search]:checked` rule, untouched here. */ +@media screen and (min-width: 60em) { + [data-md-color-scheme="slate"] .md-search__form { + background-color: rgba(0, 0, 0, 0.6); + } + + [data-md-color-scheme="slate"] .md-search__form:hover { + background-color: rgba(0, 0, 0, 0.72); + } +} + [data-md-color-scheme="slate"] .md-header::before { content: ""; position: absolute; @@ -478,6 +497,20 @@ html:focus-within::-webkit-scrollbar-thumb { color: var(--pt-nav-active); } +/* Inactive tabs: --pt-text-secondary instead of the plain ink they'd + otherwise inherit from .md-header — slightly lighter, and matching what + .nested-tabs__label already gets by default at desktop width (see + --md-nested-tabs-label-color above), so both breakpoints read the same + way. Light mode only (dark mode's own ink/secondary pairing is + unaffected). :not(.md-tabs__item--active) is required here because the + attribute selector makes this MORE specific than the active-tab rule + above (.md-tabs__item--active .md-tabs__link, two classes with no + attribute) — without it this would always win and overwrite the active + tab's color too. */ +[data-md-color-scheme="default"] .md-tabs__item:not(.md-tabs__item--active) .md-tabs__link { + color: var(--pt-text-secondary); +} + /* The desktop version of this same active-parent color (above the mkdocs-nested-tabs plugin's 76.234375em breakpoint, where .md-tabs is hidden and replaced by that plugin's own .nested-tabs row — a separate