From d7c508975c63e7328d0bb0a68d72524ff7b14df4 Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Fri, 18 Sep 2026 00:23:29 -0700 Subject: [PATCH 1/7] add codespell and toggle to readme --- README.md | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 09de406..6c9e9aa 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,9 @@ Quick cheatsheet for basic Python. **This is a casual and unpolished personal project, started in Aug '26.** -I wrote and built this from scratch — it started as a few quick-reference explanations on loops and lists for high-school intro-Python students working on their first projects, and evolved from there. I couldn't find a resource my students would consistently use that had: +I wrote and built this from scratch, not as a complete Python language reference, but as a visualization of my mental model of how Python works. + + It started as a few quick-reference explanations on loops and lists for high-school intro-Python students working on their first independent projects, and evolved from there. I couldn't find a resource my students would consistently use that had: - simple explanations for beginners without technical jargon - no advanced topics that intimidate or overwhelm beginners @@ -52,12 +54,14 @@ On the homepage there is a compacted quick reference cheatsheet that includes mo **Add-on libraries** -- **Utilities** — collections, datetime, random +- **Utilities** — collections, datetime, math, random, re, time - **Data analysis** — csv, matplotlib, NumPy, pandas - **APIs** — json, requests - **Image editing** — Pillow - **Computer vision** — OpenCV - **Desktop UIs** — Tkinter +- **Games** — turtle +- **Testing** — pytest ## Site generator @@ -110,6 +114,15 @@ A syntax highlighter, which colors code in the browser. It handles both the stat A diagram renderer, which draws flowcharts and diagrams from a plain-text description. Fenced `mermaid` blocks in the Markdown are rendered to SVG on page load. +### Essentials / Advanced toggle + +A two-option [switch](docs/javascripts/essentials_toggle.js) that lets a reader hide everything beyond a first-pass beginner curriculum. Content is opted into hiding by marking it `data-advanced="true"`: + +- On a `##`/`###` heading inside a content page (e.g. functions.md's `## Decorators`), it hides that heading plus every sibling up to the next heading of the same or higher level, and removes the matching entry from the `toc.integrate` sidebar — so there's no dead nav link to something that's hidden. +- On a homepage card-grid row, it hides just that row; `data-advanced="card"` hides an entire homepage card instead, for a whole linked page rather than one section. + +Each marking is independent — there's no shared list of "advanced" topics to keep in sync, just the attribute at each spot in the Markdown. State persists in `localStorage` and applies on every page (also settable via a `?simplified=true`/`false` URL param, for sharing a pre-set link). If a visible link points at a heading that's currently hidden (e.g. collections.md's cheat-sheet table linking to `#tuples`), following it flips the toggle back to Advanced and reveals the target instead of landing on nothing. + ## Theme ### Custom CSS @@ -171,6 +184,7 @@ The standard Python test runner, which discovers `test_*` functions across the r because it needs editorial judgment a text-only check can't make. - `tests/test_accessibility.py` is a static (no-browser) regression check for a specific accessibility bug pattern (an `outline: none` with no `:focus-visible` replacement). +- `tests/test_typos.py` runs [codespell](#codespell) over the site's prose sources. - The browser-based accessibility tier (`test_accessibility_browser.py`, `test_accessibility_runnable.py`, `test_accessibility_keyboard.py`) renders real pages with Playwright and checks: axe-core over representative pages in light/dark mode and at @@ -178,6 +192,10 @@ The standard Python test runner, which discovers `test_*` functions across the r focus order, the output live region); and keyboard navigation (skip link, a visible focus ring on every tab stop, no positive tabindex, palette toggle reachable). It's the heaviest part of the suite — needs `playwright install chromium` above and launches a real browser. +- `tests/test_essentials_toggle.py` is a browser test (same Playwright setup) for the + Essentials/Advanced toggle described above: the default (Advanced) state, that + `?simplified=true` hides marked content and carries onto a page's own heading + TOC entry, + and the link-recovery behavior for a visible link into hidden content. ### [Playwright](https://playwright.dev/) From 42d8639f051e000b977e2627838c34475e413d63 Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Fri, 18 Sep 2026 00:24:03 -0700 Subject: [PATCH 2/7] style advanced tags and checklist edits --- docs/index.md | 21 ++++--- docs/style.md | 156 ++++++++++++++++++++++++++++++-------------------- 2 files changed, 109 insertions(+), 68 deletions(-) diff --git a/docs/index.md b/docs/index.md index f158da5..969fce2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -519,26 +519,33 @@ hide: - :material-palette-outline:{ .lg .middle } [__Style__](style.md) - Conventions for standardized and readable Python. + Readable Python code, and polished UI. - [**`checklist`**](style.md#checklist) + [**`checklist`**](style.md#script-checklist) [**`linter`**](style.md#linter-tool) [**`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) - [`docstrings`](style.md#docstrings) [`indentation`](style.md#indentation) - [`naming`](style.md#naming) [`order`](style.md#file-order) [`quote style`](style.md#quote-style) - [`whitespace`](style.md#whitespace) + {: data-advanced="true" } - [**`Pythonic patterns`**](style.md#pythonic-patterns) + [**`Pythonic patterns`**](style.md#pythonic-patterns): + [`is None`](style.md#is-none-instead-of-none) + + [`truthy checks`](style.md#truthy-checks) + [`enumerate()`](style.md#enumerate-instead-of-range) + {: data-advanced="true" } - [**`Polish`**](style.md#polish): + [**`Polished UX`**](style.md#polished-ux): [`banners`](style.md#banners) [`input validation`](style.md#input-validation) [`menus`](style.md#menus) diff --git a/docs/style.md b/docs/style.md index 891d30f..f205de4 100644 --- a/docs/style.md +++ b/docs/style.md @@ -6,40 +6,68 @@ description: >- # :material-palette-outline:{ .lg .middle } Style -Code that works isn't automatically code that's easy to live with — following a shared set of conventions is what keeps a Python file manageable as it grows past a few lines. +Code that works isn't automatically code that's easy to read and maintain. -- **Consistency** — code that follows the same conventions everywhere reads the same, no matter who wrote which part -- **Faster to learn** — a new file feels familiar instead of like starting over, since the same patterns keep repeating -- **Easier to debug** — a predictable layout means you already know where to look when something breaks -- **Easier for others to work with** — working on a collaborative project means others **review** your code before approving it and **merge** it in with everyone else's changes; consistent style means they can focus on what you actually changed, instead of different formatting choices +- **Consistent:** following the same conventions reads the same, no matter who wrote it +- **Faster to learn:** a new file feels familiar, uses the same patterns +- **Easier to debug:** you know where to look when something breaks +- **Effective collaboration:** when your code is **reviewed** so it can be **merged** in with everyone else's changes, consistent style means it's clearer what you actually changed, instead of needing to compare conflicting formatting choices
-## Checklist +## Script style checklist -A few things worth double-checking before calling a script finished — each links to the full rule further down this page. Run a [linter](#linter-tool) first, since it catches most of this automatically; what's left afterward has to be checked by eye. +**PEP 8 Formatting** -- [ ] **[Run a linter check](#linter-tool)** — catches many of the following automatically, but it can be good practice to check manually instead to get familiar with writing it correctly from the start: - - [ ] **[File Order](#file-order)** — standardized file layout - - [ ] **[Mutable default arguments](functions.md#defining-a-function)** — a default list/dict shared across every call - - [ ] **[`is None` instead of `== None`](#common-patterns)** — a real correctness risk, not just style - - [ ] **[`with open(...)` instead of manual `open()`/`close()`](files.md#opening-a-file)** — avoids a file left open if something goes wrong - - [ ] **[Catch specific exceptions](errors.md#catch-specific-exceptions)** — no bare `except:` swallowing errors you didn't expect - - [ ] **[Naming](#naming)** — does every variable and function name say what it holds? - - [ ] **[Docstrings](#docstrings)** — does every function and file explain what it does? - - [ ] **[Truthy checks instead of `len(x) > 0`](#common-patterns)** — test a collection directly - - [ ] **[`enumerate()` instead of `range(len(...))`](#common-patterns)** — loop with both index and item at once +- [ ] **[Run a linter](#linter-tool) to fix PEP 8 issues** — catches most of the following automatically: - [ ] **[Indentation](#indentation)** — 4 spaces per level, never tabs + { data-advanced="true" } - [ ] **[Blank lines](#blank-lines)** — two around top-level functions/classes, one between methods - [ ] **[Whitespace](#whitespace)** — spaces around operators, but not around a keyword argument's `=` - [ ] **[Comments](#comments)** — two spaces before an inline `#`, one space after -- [ ] **[Keep functions focused](functions.md#keep-functions-focused)** — does each function do just one job, with no repeated logic a [linter](#linter-tool) won't flag on its own? -- [ ] **[Constants](#constants)** — are unchanging numbers pulled out into named `ALL_CAPS` values? -- [ ] **[Quote style](#quote-style)** — one quote style used consistently throughout the file + { data-advanced="true" } + - [ ] **[Quote style](#quote-style)** — one quote style used consistently throughout the file + { data-advanced="true" } +- [ ] **[File order](#file-order)** — standardized file layout; not something a linter checks by default + { data-advanced="true" } + +**Naming & documentation** + +- [ ] **[File names](workspace.md#step-2-write-and-run-a-python-file)** — `snake_case.py`, no hyphens or spaces +- [ ] **[Variable and function names](#naming)** — does each describe what it holds? +- [ ] **[Docstrings](#docstrings)** — does every function and file explain what it does? - [ ] **[Type hints](functions.md#type-hints)** — used on a function signature where the types aren't obvious? + { data-advanced="true" } +- [ ] **[Constants](#constants)** — are unchanging numbers pulled out into named `ALL_CAPS` values? + { data-advanced="true" } + +**Pythonic patterns** + +- [ ] **[Mutable default arguments](functions.md#defining-a-function)** — a default list/dict shared across every call +- [ ] **[`is None` instead of `== None`](#is-none-instead-of-none)** — a real correctness risk, not just style +- [ ] **[`with open(...)` instead of manual `open()`/`close()`](files.md#opening-a-file)** — avoids a file left open if something goes wrong +- [ ] **[Catch specific exceptions](errors.md#catch-specific-exceptions)** — no bare `except:` swallowing errors you didn't expect +- [ ] **[Truthy checks instead of `len(x) > 0`](#truthy-checks)** — test a collection directly + { data-advanced="true" } +- [ ] **[`enumerate()` instead of `range(len(...))`](#enumerate-instead-of-range)** — loop with both index and item at once + { data-advanced="true" } - [ ] **[Tuple unpacking instead of a temporary variable](collections.md#packing-and-unpacking)** — swapping two variables directly -- [ ] **[File names](workspace.md#step-2-write-and-run-a-python-file)** — `snake_case.py`, no hyphens or spaces -- [ ] **[Escape sequences](foundations.md#escape-sequences)** — `\n`/`\t` and separator rows used to space out console output + +**Structure** + +- [ ] **[Keep functions focused](functions.md#keep-functions-focused)** — does each function do just one job? + +**Polished UX** + +- [ ] **For terminal input:** + - [ ] **[Input validation](#input-validation)** — re-asks instead of crashing on a bad or missing value + - [ ] **[Menus](#menus)** — a clear list of options instead of guessing what to type +- [ ] **For terminal display:** + - [ ] **[Printing output](#printing-output)** — escape sequences, multi-line strings, and formatted variables read cleanly + - [ ] **[Banners](#banners)** — a decorative box or header instead of a bare print statement + - [ ] **[Progress bars](#progress-bars)** — visible feedback during a delay instead of a silent pause + - [ ] **[Unicode symbols](#unicode-symbols)** — box-drawing, arrows, and checkmarks instead of plain ASCII + - [ ] **[Randomize messages](#randomize-messages)** — varied responses instead of the same output every run
@@ -80,7 +108,7 @@ A **formatter** tool (either separate, or a combined linter+formatter), actually Python runs styled and unstyled code identically, so following PEP 8 doesn't make a script more *correct* — it makes it more *predictable* to read. Anyone who's used Python before recognizes the shape of PEP 8-styled code, so sticking to it means less friction reading someone else's code, and less friction when someone else reads yours. -### File order +### File order { data-advanced="true" } A Python file conventionally follows the same layout, top to bottom.[^order-pep8] @@ -133,7 +161,7 @@ length_ft = 4.5 # clear at a glance A short name is fine when its scope is short too — `for s in species:` is common, since `s` only exists for the one line inside the loop. -### Constants +### Constants { data-advanced="true" } A **constant** is a variable whose value isn't meant to change while the program runs — written in `ALL_CAPS` by convention, so it's easy to tell apart from a regular variable at a glance. Defining one instead of repeating a raw number (a "magic number") gives that number a name explaining what it means. @@ -148,7 +176,7 @@ if length_ft > MAX_TYPICAL_LENGTH_FT: Constants are usually defined near the top of a file, so they're easy to find and adjust later — see [File Order](#file-order) above. -### Quote style +### Quote style { data-advanced="true" } Python treats `'single'` and `"double"` quotes identically for strings — PEP 8 doesn't prefer one over the other, just pick one as your default and stick with it throughout a file, rather than mixing both without reason. (This site uses double quotes.) The one except‌ion: switch to the other quote character for a string that itself contains a quote, rather than escaping it with a backslash. @@ -202,7 +230,7 @@ species = "ball python" length_ft = 4.5 ``` -### Indentation +### Indentation { data-advanced="true" } Python uses indentation, not braces, to mark a block — PEP 8's rule is 4 spaces per level, never tabs (mixing the two causes real errors, not just style complaints). @@ -248,7 +276,7 @@ def describe(species, length_ft=4.5): # PEP 8 ... ``` -### Comments +### Comments { data-advanced="true" } An inline comment needs at least two spaces before the `#` and one space after it; a block comment on its own line follows the same one-space-after rule. @@ -273,44 +301,50 @@ There's no single tool that reliably flags all "unpythonic" code the way PEP 8 h Other programming languages have different features and patterns, so if code is translated from another language into Python it might not be written very clearly. Pythonic code tends to be less buggy and faster. A few of these a beginner tends to write out longhand before learning the built-in shortcut, roughly most to least common: -- **Truthy checks instead of `len(x) > 0`** — test a collection directly; a non-empty list is already truthy +### Truthy checks instead of `len(x) > 0` { #truthy-checks data-advanced="true" } - ```python-ref - if len(species) > 0: # works, but not Pythonic - print("found some") +Test a collection directly — a non-empty list is already truthy. - if species: # Pythonic — a non-empty list is already truthy - print("found some") - ``` +```python-ref +if len(species) > 0: # works, but not Pythonic + print("found some") -- **`enumerate()` instead of `range(len(...))`** — loop with both the index and the item at once, instead of indexing into the list by hand +if species: # Pythonic — a non-empty list is already truthy + print("found some") +``` - ```python-ref - for i in range(len(species)): # manual indexing - print(i, species[i]) +### `enumerate()` instead of `range(len(...))` { #enumerate-instead-of-range data-advanced="true" } - for i, s in enumerate(species): # Pythonic — enumerate() hands back both - print(i, s) - ``` +Loop with both the index and the item at once, instead of indexing into the list by hand. -- **`is None` instead of `== None`** — checking against `None` is a check of identity, not equality, so `is` is the correct tool +```python-ref +for i in range(len(species)): # manual indexing + print(i, species[i]) - ```python-ref - length_ft = None - if length_ft == None: # works, but not Pythonic - print("unknown length") +for i, s in enumerate(species): # Pythonic — enumerate() hands back both + print(i, s) +``` - if length_ft is None: # Pythonic — `is` is the correct tool for a None check - print("unknown length") - ``` +### `is None` instead of `== None` { #is-none-instead-of-none } + +Checking against `None` is a check of identity, not equality, so `is` is the correct tool. + +```python-ref +length_ft = None +if length_ft == None: # works, but not Pythonic + print("unknown length") + +if length_ft is None: # Pythonic — `is` is the correct tool for a None check + print("unknown length") +```
-## Polish +## Polished UX -The terminal is a **user interface**, and just like an app or website, it can be creatively designed within it's limitations to be more interactive, engaging, and readable. +The terminal is a **user experience** with its own **interface**, and just like an app or website, it can be creatively designed within it's limitations to be more interactive, engaging, and readable. ### Printing output @@ -318,13 +352,13 @@ The terminal is a **user interface**, and just like an app or website, it can be An **escape sequence** is a backslash followed by a letter, standing in for a character that couldn't otherwise appear in the string. -| Escape | Does | Shows up below in | -|---|---|---| -| `\n` | starts a new line | the [banner](#banners)'s greeting, printed on the line after the box | -| `\t` | inserts a tab | lining up columns of output (Foundations) | -| `\r` | returns the cursor to the start of the line, without moving down | redrawing a [progress bar](#progress-bars) in place | -| `\"`, `\'` | a literal quote character | a quote inside a string using the same quote mark | -| `\\` | a literal backslash | a Windows-style file path (Foundations) — the [original ASCII](#original-ascii) below sidesteps needing it with a raw string instead | +| Escape | Prints | +|---|---| +| `\n` | a new line | +| `\t` | a tab, as in lining up columns of output | +| `\"`, `\'` | a literal quote character | +| `\\` | a literal backslash | +| `\r` | returns the cursor to the start of the line, as in a [progress bar](#progress-bars)| #### Multi-line strings @@ -542,13 +576,13 @@ Wrap the same pattern in a `while` loop that reprints the menu and `break`s once ```python-ref while True: print(""" -╔══════════════════════╗ +╔═══════════════════════╗ ║ FIELD GUIDE MENU ║ -╠══════════════════════╣ +╟───────────────────────╢ ║ 1. Log a sighting ║ ║ 2. Look up a species ║ ║ 3. Quit ║ -╚══════════════════════╝ +╚═══════════════════════╝ """) choice = input("> ") if choice == "1": From 5cb6d955369406a03dd169646e5e944498d1425b Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Fri, 18 Sep 2026 02:39:00 -0700 Subject: [PATCH 3/7] list other resources in about page --- docs/about.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/about.md b/docs/about.md index 23afeca..99c329f 100644 --- a/docs/about.md +++ b/docs/about.md @@ -53,6 +53,25 @@ More about me at [lukasherman.com](https://lukasherman.com).
+## Other resources + +This site isn't a comprehensive Python guide, so here's where to go for more. + +For beginners: + +- [W3Schools Python Tutorial](https://www.w3schools.com/python/) — syntax reference with a live editor for every example +- [Real Python](https://realpython.com/) — longer tutorials and articles that go deeper on individual topics +- [Python's own tutorial](https://docs.python.org/3/tutorial/) — official, terse, but authoritative + +For more advanced Python: + +- [Effective Python](https://effectivepython.com/) — specific, itemized advice on writing idiomatic, well-structured Python +- [PEP 8](https://peps.python.org/pep-0008/) — the official style guide + +
+ +
+ ## Helpful feedback Spotted a mistake, or want to see something added? Let me know! From 2595956ea369885ee2164104fff318b788325179 Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Fri, 18 Sep 2026 02:39:45 -0700 Subject: [PATCH 4/7] minor edits --- README.md | 4 ++++ STRUCTURE.md | 7 ++++--- docs/collections.md | 8 ++++---- docs/index.md | 2 +- docs/style.md | 20 ++++++-------------- 5 files changed, 19 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 6c9e9aa..0484adc 100644 --- a/README.md +++ b/README.md @@ -205,6 +205,10 @@ A browser-automation library, which drives a real browser from code to load page An accessibility rule engine, which scans a rendered page's DOM for WCAG violations. It runs inside the Playwright browser against every fully rendered page. +### [codespell](https://github.com/codespell-project/codespell) + +A spell checker aimed at source code and prose, which flags known misspellings (e.g. "teh", "recieve") rather than words missing from a dictionary. `tests/test_typos.py` runs it over the site's page content and editorial docs. + ### Continuous integration 1. Work is done on the `development` branch. diff --git a/STRUCTURE.md b/STRUCTURE.md index 9511cfb..e47b8c2 100644 --- a/STRUCTURE.md +++ b/STRUCTURE.md @@ -138,9 +138,10 @@ staying inline.** bold/emphasis markup, not literal underscores, so `### Overriding __init__()` renders (and slugifies) as "Overriding **init**()" with the underscores silently eaten. Confirm any heading change like this against the real built HTML, not just the source Markdown. -- **`####` is reserved** for `index.md`'s homepage category boxes and genuinely deep - library-page content (e.g. `libraries/pillow.md`'s per-method sections) — most content pages - should never need to go past `###`. +- **`####` is a normal fourth level**, used under the same three conditions as `###` above, for + when a `###` section itself needs breaking down further (e.g. `types.md`'s `### int`/`### float` + each splitting into `#### Arithmetic`, `#### Convert`, etc.). Used throughout content pages, not + just `index.md`'s homepage category boxes or library pages. ### Homepage keyword deep-links (`index.md`, `libraries/index.md`) diff --git a/docs/collections.md b/docs/collections.md index 2ce9ddf..843416a 100644 --- a/docs/collections.md +++ b/docs/collections.md @@ -14,7 +14,7 @@ A **collection** is a single object that groups multiple values (like [basic typ |------|---------|:-----------------:|:-------:|------------| | **`list`** |
["ball", "burmese"]
| position # | :material-check:{ .pt-icon-success } |
  • An ordered group of items you can freely add to, remove from, or reorder
  • Not sure? Start here — the default, general-purpose choice
| | **`dictionary "dict"`** |
{
  "species": "ball",
  "length_ft": 5
}
| Name of a key | :material-check:{ .pt-icon-success } |
  • Values stored under names ("keys") instead of position, like `species`, `length_ft`
  • Use it to look values up by name
  • Can't have duplicate keys
| -| **`tuple`** |
("ball", "burmese")
| position # | :material-close:{ .pt-icon-fail } |
  • Like a list, but fixed — can't be changed once created
  • Values that should stay exactly as they are, like a coordinate pair
| +| **`tuple`** |
("ball", "burmese")
| position # | :material-close:{ .pt-icon-fail } |
  • Like a list, but it's sequence of items can't be changed once created
  • Values that should stay exactly as they are, like a coordinate pair
| | **`set`** |
{"ball", "burmese"}
| Membership (`in`) | :material-check:{ .pt-icon-success } |
  • An unordered group where duplicates are automatically dropped
  • Use it for fast "is this in here?" checks
|
@@ -28,10 +28,10 @@ A **collection** is a single object that groups multiple values (like [basic typ ```python-ref weights = [5, 3, 6] - type(weights) # + type(weights) # - isinstance(5, list) # True - isinstance(5, dict) # False + isinstance(weights, list) # True + isinstance(weights, dict) # False ```
diff --git a/docs/index.md b/docs/index.md index 969fce2..ed920ed 100644 --- a/docs/index.md +++ b/docs/index.md @@ -19,7 +19,7 @@ hide: |---|---|---| | **Writing & struggling with code** | :material-check:{ .pt-icon-success } Productive struggle is what **builds understanding**
  • You can solve the problem **again** on your own
  • **Adapt** the answer, and **catch** when it's wrong
| :material-close:{ .pt-icon-fail } Being handed the answer skips [the friction that builds understanding](https://bjorklab.psych.ucla.edu/wp-content/uploads/sites/13/2016/04/EBjork_RBjork_2011.pdf)
  • If you skip that struggle, you **won't develop the knowledge** to solve the problem again, adapt the answer, or recognize when it's wrong.
| | **Reading & verifying code** | :material-check:{ .pt-icon-success } **Understanding programming fundamentals** makes AI more useful — you can read and evaluate code you didn't write
  • Spot mistakes
  • Understand **why** a solution works
  • Communicate your problem to AI more effectively
| :material-close:{ .pt-icon-fail } Inefficient communication with AI **if you don't fully understand **what's going on, and AI code can look correct while being wrong or insecure
  • You can't tell **why** a solution works
  • Researchers are already documenting this skill gap in [students who rely on AI code generation](https://dl.acm.org/doi/10.1145/3617367) before they've [built their own foundation](https://dl.acm.org/doi/10.1145/3624720)
  • One [Stanford study](https://dl.acm.org/doi/10.1145/3576915.3623157) found developers using AI wrote **less** secure code — but were **more** confident it was secure
| - | **On the job** | :material-check:{ .pt-icon-success } Employers are still hiring for understanding, not prompting
  • Code review, debugging, and interviews all test whether you can reason about code and judge whether it's correct
  • Learning to program on your own turns AI into a **tool you can direct and verify**, instead of one you're assuming got it right
| :material-close:{ .pt-icon-fail } AI will likely be available at work too — but it isn't what will get you hired | + | **On the job** | :material-check:{ .pt-icon-success } Programming work still requires understanding:
  • Code review, debugging, system design, and interviews all test whether you can reason about code and judge whether it's correct
  • Learning to program on your own turns AI into a **tool you can direct and verify**, instead of one you're assuming got it right
| :material-close:{ .pt-icon-fail } AI will likely be available at work too — but it isn't what will get you hired |
diff --git a/docs/style.md b/docs/style.md index f205de4..94eb085 100644 --- a/docs/style.md +++ b/docs/style.md @@ -45,7 +45,7 @@ Code that works isn't automatically code that's easy to read and maintain. - [ ] **[Mutable default arguments](functions.md#defining-a-function)** — a default list/dict shared across every call - [ ] **[`is None` instead of `== None`](#is-none-instead-of-none)** — a real correctness risk, not just style -- [ ] **[`with open(...)` instead of manual `open()`/`close()`](files.md#opening-a-file)** — avoids a file left open if something goes wrong +- [ ] **[`with open(...)` instead of manual `open()`/`close()`](files.md#with)** — avoids a file left open if something goes wrong - [ ] **[Catch specific exceptions](errors.md#catch-specific-exceptions)** — no bare `except:` swallowing errors you didn't expect - [ ] **[Truthy checks instead of `len(x) > 0`](#truthy-checks)** — test a collection directly { data-advanced="true" } @@ -344,7 +344,7 @@ if length_ft is None: # Pythonic — `is` is the correct tool for ## Polished UX -The terminal is a **user experience** with its own **interface**, and just like an app or website, it can be creatively designed within it's limitations to be more interactive, engaging, and readable. +The terminal is a **user experience** with its own **interface**, and just like an app or website, it can be creatively designed within its limitations to be more interactive, engaging, and readable. ### Printing output @@ -438,7 +438,7 @@ print(r""" #### Unicode expansion -**Unicode** started in 1991 and replaced ASCII with a growing set: it started with the same 128 characters ASCII already had, and is now at 150,000 characters. Because it keeps growing, something built before a character existed may show a blank box or `?`. Python source files use UTF-8 which can represent every Unicode character — so any of these work in a Python file, although some editors and terminals may not *display* it correctly. +**Unicode** started in 1991 and expanded on ASCII with a larger growing set: it started with the same 128 characters ASCII already had, and is now at 150,000 characters. Because it keeps growing, something built before a character existed may show a blank box or `?`. Python 3 uses UTF-8 to encode its characters, so you can include Unicode characters directly in your Python files. However, your editor or terminal may still lack a font that can display a particular character. Emoji are part of this too: the character itself (😀, 🐍) is a Unicode character, but the specific picture a device displays is drawn by each platform, which is why the same emoji looks different on iPhone vs. Android. @@ -647,7 +647,7 @@ Loading... Loading... done! ``` -Print with [`end="\r"`](types.md#combine) instead, and each new line overwrites the last one instead of stacking below it — enough to build an animated progress bar out of characters. +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. ```python-ref import time @@ -681,17 +681,9 @@ print("done!") ``` ```bash -⠋ -⠙ -⠹ -⠸ -⠼ -⠴ -⠦ -⠧ -⠇ -⠏ +⠏ ``` +The above character changes in place, so you see an animation cycling through the steps. ### Randomize messages From 8e32db2cfcc8a616ad7ffac37a5160ed52dfd9d0 Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Fri, 18 Sep 2026 02:40:08 -0700 Subject: [PATCH 5/7] rewrite files page --- docs/files.md | 321 ++++++++++++++++++++++++++++++++------- docs/index.md | 32 +++- docs/libraries/turtle.md | 2 +- 3 files changed, 291 insertions(+), 64 deletions(-) diff --git a/docs/files.md b/docs/files.md index ed984c7..cc5e979 100644 --- a/docs/files.md +++ b/docs/files.md @@ -4,11 +4,11 @@ description: >- and other formats, with runnable examples. --- -# :material-file-document-outline:{ .lg .middle } File I/O +# :material-file-document-outline:{ .lg .middle } File Read/Write -A file lets a program keep data around after it ends — `print()` shows something on screen, but it's gone the moment the program stops. Saving to a file means that data is still there the next time it runs. +Instead of only printing output to the terminal, you can have the program save data to a file on your computer so data stays after the program ends, or read data from a file. -**I/O** stands for **input/output** — the general term for a program reading data in or sending data out, to somewhere other than just the screen. `print()` and `input()`, already covered on the [Foundations](foundations.md#print-function) page, are technically I/O too — output to the terminal, input from the keyboard. "File I/O" narrows that down to reading from and writing to files on disk specifically. +For how to pull code **from another `.py` file** into your program, that's in [Modules & Imports](modules.md#importing-modules).
@@ -27,35 +27,63 @@ flowchart LR
-## Opening a file +## Opening and closing files -`open()` returns a file object to read from or write to. Wrap it in a `with` block so it's closed automatically once you're done, even if something goes wrong partway through. +`open()` returns a file object to read from or write to. -```python-ref -with open("notes.txt", "w") as file: - file.write("ball python, 4.5 ft") +### File paths + +`open("notes.txt", ...)` is a **relative path** — Python looks for `notes.txt` in the program's **working directory**, the folder it's currently running from, which isn't necessarily the folder the `.py` file itself lives in. Reaching a file somewhere else means either writing out the folders in between, or an **absolute path** — the full location starting from the filesystem's root, which works the same no matter what the working directory is. + +Say `notes.txt` is inside a `snake_data` folder in your Documents folder instead of next to your script. To find its exact path: + +=== "macOS" -print("saved") + Right-click the file in Finder, hold ⌥ Option, and choose **Copy "notes.txt" as Pathname** — or drag the file straight into a Terminal window to have the path typed out for you. + + ```text + /Users/luka/Documents/snake_data/notes.txt + ``` + +=== "Windows" + + Shift+right-click the file in File Explorer and choose **Copy as path**. + + ```text + C:\Users\luka\Documents\snake_data\notes.txt + ``` + +That path is what goes inside `open()`: + +```python-ref +with open("/Users/luka/Documents/snake_data/notes.txt", "r") as file: + print(file.read()) ``` -The second argument is the **mode** — what you intend to do with the file: +On Windows, write the path with an `r` prefix (`r"C:\Users\luka\..."`) or doubled backslashes (`"C:\\Users\\luka\\..."`) — a single backslash inside a normal string starts an escape sequence, which isn't what a Windows path means. -| Mode | Meaning | -|------|---------| -| `"r"` | Read (default) — the file must already exist | -| `"w"` | Write — creates the file if it doesn't exist, erases its contents if it does | -| `"a"` | Append — creates the file if it doesn't exist, adds to the end if it does | +Every runnable example on this page opens a plain filename like `"notes.txt"` — that's a relative path into the sandbox's own working directory, the same reason it works without ever specifying a folder. -??? warning "Without `with`, you have to call `file.close()` yourself" - `with` closes the file automatically once its block ends — even if the code inside raises an error. Without it, you have to call `file.close()` yourself, and forgetting to means your writes might never actually reach the file (still sitting in a buffer), or the **file stays locked** for anything else trying to open it. +### with - ```python-ref - file = open("notes.txt", "w") +`with` runs the indented block below it, then closes the file automatically once the block ends — whether it finishes normally or raises an error partway through. `open(...)` produces the file object; `as file` is what makes it available under that name inside the block. + +```python-ref +with open("notes.txt", "w") as file: file.write("ball python, 4.5 ft") - file.close() # easy to forget - ``` -??? run "Run an opening a file example" +print("saved") # file is already closed here — "saved" sits outside the with block +``` + +Without `with`, the same thing takes an explicit `.close()` call — easy to forget, and skipping it means what you wrote might not be saved to the file yet — Python can hold new content in memory for a while before actually writing it out — or the file stays locked for anything else trying to open it. + +```python-ref +file = open("notes.txt", "w") +file.write("ball python, 4.5 ft") +file.close() # easy to forget +``` + +??? run "Run a with example" All the examples above, combined into one script: ```python @@ -65,11 +93,26 @@ The second argument is the **mode** — what you intend to do with the file: print("saved") ``` +### Modes options + +The second argument to `open()` is the **mode** — what you intend to do with the file: + +| Mode | Meaning | +|------|---------| +| `"r"` | Read (default) — the file must already exist | +| `"w"` | Write — creates the file if it doesn't exist, erases its contents if it does | +| `"a"` | Append — creates the file if it doesn't exist, adds to the end if it does | +| `"x"` | Create — creates the file, but raises an error if it already exists | +
-## Reading a file +## Read + +### Modes + +#### "r" read existing Say `notes.txt` already exists — written by an earlier run, or typed by hand in a text editor — and looks like this, one snake per line: @@ -79,7 +122,36 @@ burmese python, 12 ft boa, 8 ft ``` -`.read()` returns the whole thing as one string, newlines and all. +Opening a file that doesn't exist in `"r"` mode raises `FileNotFoundError` instead of creating one — unlike `"w"`/`"a"`/`"x"`, `"r"` never creates a file. + +```python-ref +with open("notes.txt", "r") as file: + print(file.read()) + +open("missing.txt", "r") # FileNotFoundError: [Errno 2] No such file or directory: 'missing.txt' +``` + +??? run "Run a read existing example" + All the examples above, combined into one script: + + ```python + with open("notes.txt", "w") as file: + file.write("ball python, 4.5 ft\nburmese python, 12 ft\nboa, 8 ft\n") + + with open("notes.txt", "r") as file: + print(file.read()) + + try: + open("missing.txt", "r") + except FileNotFoundError as e: + print(e) + ``` + +### Functions + +#### Whole file + +`.read()` returns the whole thing as one string, newlines and all. It also takes an optional character count, returning just that many characters instead of the whole file. ```python-ref with open("notes.txt", "w") as file: @@ -89,9 +161,30 @@ with open("notes.txt", "r") as file: text = file.read() print(text) + +with open("notes.txt", "r") as file: + print(file.read(4)) # "ball" ``` -`.readlines()` instead returns a list, one string per line, each still ending in a trailing `\n`. Looping over the file object directly reads it the same way, one line at a time, without holding the whole list in memory at once. +??? run "Run a whole file example" + All the examples above, combined into one script: + + ```python + with open("notes.txt", "w") as file: + file.write("ball python, 4.5 ft\nburmese python, 12 ft\nboa, 8 ft\n") + + with open("notes.txt", "r") as file: + text = file.read() + + print(text) + + with open("notes.txt", "r") as file: + print(file.read(4)) + ``` + +#### By line + +`.readlines()` returns a list, one string per line, each still ending in a trailing `\n`. Looping over the file object directly reads it the same way, one line at a time, without holding the whole list in memory at once. `.readline()` reads a single line and advances to the next — call it repeatedly to step through a file by hand, though looping does the same thing more naturally. ```python-ref with open("notes.txt", "r") as file: @@ -103,18 +196,13 @@ with open("notes.txt", "r") as file: print(line.strip()) # ball python, 4.5 ft / burmese python, 12 ft / boa, 8 ft ``` -??? run "Run a reading a file example" +??? run "Run a line-by-line example" All the examples above, combined into one script: ```python with open("notes.txt", "w") as file: file.write("ball python, 4.5 ft\nburmese python, 12 ft\nboa, 8 ft\n") - with open("notes.txt", "r") as file: - text = file.read() - - print(text) - with open("notes.txt", "r") as file: lines = file.readlines() print(lines) @@ -124,13 +212,56 @@ with open("notes.txt", "r") as file: print(line.strip()) ``` +#### Seek and tell { data-advanced="true" } + +`.tell()` returns the current position in the file, as a character count from the start. `.seek(position)` moves back to a given position, letting you re-read part of a file without closing and reopening it. + +```python-ref +with open("notes.txt", "r") as file: + file.read() + print(file.tell()) # 52 — at the end, after reading everything + + file.seek(0) + print(file.read(4)) # "ball" — back at the start +``` + +??? run "Run a seek and tell example" + All the examples above, combined into one script: + + ```python + with open("notes.txt", "w") as file: + file.write("ball python, 4.5 ft\nburmese python, 12 ft\nboa, 8 ft\n") + + with open("notes.txt", "r") as file: + file.read() + print(file.tell()) + + file.seek(0) + print(file.read(4)) + ``` +
-## Writing multiple lines +## Write + +### Modes + +#### "w" overwrite + +`"w"` erases whatever was already in the file before writing anything new — opening a file you meant to add to with `"w"` is a common way to accidentally lose data. + +
+ +```mermaid +flowchart LR +w1@{ shape: paper-tape, label: "ball python, 4.5 ft" } -->|"w": erased, then written| w2@{ shape: paper-tape, label: "blood python, 3 ft" } +``` + +

FIG: "w" erases the file, then writes the new content

-`.write()` doesn't add a newline for you — add one yourself at the end of each line, usually by looping over a list. +
```python-ref species = ["ball python", "burmese python", "boa"] @@ -154,36 +285,18 @@ with open("notes.txt", "w") as file: print(file.read()) ``` -
- -
- -## Appending vs. overwriting +#### "a" append -`"w"` erases whatever was already in the file before writing anything new — opening a file you meant to add to with `"w"` is a common way to accidentally lose data. Use `"a"` instead to add to the end, keeping the existing contents in place. +Use `"a"` instead to add to the end, keeping the existing contents in place — compare against `"w"` above.
```mermaid -flowchart TB - -subgraph modeW["Overwrite"] -direction LR -w1@{ shape: paper-tape, label: "ball python, 4.5 ft" } -->|"w": erased, then written| w2@{ shape: paper-tape, label: "blood python, 3 ft" } -end - -subgraph modeA["Append"] -direction LR +flowchart LR a1@{ shape: paper-tape, label: "ball python, 4.5 ft" } -->|"a": kept, plus written| a2@{ shape: paper-tape, label: "ball python, 4.5 ft
blood python, 3 ft" } -end - -modeW ~~~ modeA - -style modeW fill:none,stroke:none -style modeA fill:none,stroke:none ``` -

FIG: overwrite mode vs. append mode

+

FIG: "a" keeps the file's contents, then adds the new content to the end

@@ -210,7 +323,103 @@ with open("notes.txt", "r") as file: print(file.read()) ``` -For reading and writing rows of structured, comma-separated data specifically, see the [csv](libraries/csv.md) page under Libraries — it's built on the same `open()` and file-mode basics covered here. +#### "x" create { data-advanced="true" } + +`"x"` is for when overwriting an existing file would be a mistake — it creates the file, but raises `FileExistsError` instead of silently replacing something already there. Like `"w"`, it's write-only — reading from that same file object raises an error, so reading it back means reopening it in `"r"` mode afterward. + +```python-ref +with open("newfile.txt", "x") as file: + file.write("hello") + +with open("newfile.txt", "r") as file: + print(file.read()) # "hello" + +open("newfile.txt", "x") # FileExistsError: [Errno 17] File exists: 'newfile.txt' +``` + +??? run "Run an x mode example" + All the examples above, combined into one script: + + ```python + with open("newfile.txt", "x") as file: + file.write("hello") + + with open("newfile.txt", "r") as file: + print(file.read()) + + try: + with open("newfile.txt", "x") as file: + file.write("hello again") + except FileExistsError as e: + print(e) + ``` + +### Functions + +#### Single string + +`.write()` writes a string to the file — it doesn't add a newline for you, so add one yourself at the end of each line, usually by looping over a list. Whether that write starts the file fresh or adds onto what's already there depends on which mode you opened it with, `"w"` or `"a"`. + +```python-ref +with open("notes.txt", "w") as file: + file.write("ball python") + file.write("4.5 ft") + +with open("notes.txt", "r") as file: + print(file.read()) # "ball python4.5 ft" — no newline between the two writes +``` + +??? run "Run a single string example" + All the examples above, combined into one script: + + ```python + with open("notes.txt", "w") as file: + file.write("ball python") + file.write("4.5 ft") + + with open("notes.txt", "r") as file: + print(file.read()) + ``` + +#### Multiple strings + +`.writelines()` takes a list of strings and writes them all in one call instead of looping yourself — like `.write()`, it doesn't add newlines, so they need to already be in the strings. + +```python-ref +species = ["ball python\n", "burmese python\n", "boa\n"] + +with open("notes.txt", "w") as file: + file.writelines(species) +``` + +??? run "Run a writelines example" + All the examples above, combined into one script: + + ```python + species = ["ball python\n", "burmese python\n", "boa\n"] + + with open("notes.txt", "w") as file: + file.writelines(species) + + with open("notes.txt", "r") as file: + print(file.read()) + ```
+
+ +## Related libraries + +Everything above is plain text. For other file formats, these Libraries pages build on the same `open()` and file-mode basics covered here: + +| 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. | + +
diff --git a/docs/index.md b/docs/index.md index ed920ed..ec37c32 100644 --- a/docs/index.md +++ b/docs/index.md @@ -501,13 +501,31 @@ hide: Read and write text files on your computer. - [**`open()`**](files.md#opening-a-file) - - [**`read()`**](files.md#reading-a-file) - - [**`write()`**](files.md#writing-multiple-lines) - - [**`append`**](files.md#appending-vs-overwriting) + [**`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)
diff --git a/docs/libraries/turtle.md b/docs/libraries/turtle.md index 3aff302..c84c126 100644 --- a/docs/libraries/turtle.md +++ b/docs/libraries/turtle.md @@ -201,7 +201,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#opening-a-file), 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](../files.md#with), applied to animation instead of a file handle. ```python-ref with no_animation(): From d0d3500fe9415b31cddec23dade22707f9691bf3 Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Fri, 18 Sep 2026 02:42:05 -0700 Subject: [PATCH 6/7] remove intentional typos from readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0484adc..79562bb 100644 --- a/README.md +++ b/README.md @@ -207,7 +207,7 @@ An accessibility rule engine, which scans a rendered page's DOM for WCAG violati ### [codespell](https://github.com/codespell-project/codespell) -A spell checker aimed at source code and prose, which flags known misspellings (e.g. "teh", "recieve") rather than words missing from a dictionary. `tests/test_typos.py` runs it over the site's page content and editorial docs. +A spell checker aimed at source code and prose, which flags known misspellings against a fixed list rather than words simply missing from a dictionary. `tests/test_typos.py` runs it over the site's page content and editorial docs. ### Continuous integration From 426f100d0b987161ab1e82c4c52ed4fed5f2eb97 Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Fri, 18 Sep 2026 02:46:04 -0700 Subject: [PATCH 7/7] correct index tag link --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index ec37c32..e1cfd4b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -539,7 +539,7 @@ hide: Readable Python code, and polished UI. - [**`checklist`**](style.md#script-checklist) + [**`checklist`**](style.md#script-style-checklist) [**`linter`**](style.md#linter-tool)