Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion STRUCTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<div data-advanced="true" markdown="block">` 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 `<div data-advanced="true" markdown="block">` 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
Expand Down
File renamed without changes.
4 changes: 2 additions & 2 deletions docs/loops.md → docs/flow/loops.md
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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"

Expand Down
Loading
Loading