From f9595f34a73bfab953c3c36c9a48a3aac985ff7f Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Sat, 19 Sep 2026 17:12:05 -0700 Subject: [PATCH 1/5] remove redundant style checklist --- docs/index.md | 3 +- docs/style.md | 117 +++++++++++++++++++------------------------------- docs/types.md | 7 +-- 3 files changed, 49 insertions(+), 78 deletions(-) diff --git a/docs/index.md b/docs/index.md index a486285..9400aaf 100644 --- a/docs/index.md +++ b/docs/index.md @@ -539,8 +539,6 @@ hide: Readable Python code, and polished UI. - [**`checklist`**](style.md#script-style-checklist) - [**`linter`**](style.md#linter-tool) [**`PEP 8`**](style.md#pep-8-style-guide): @@ -557,6 +555,7 @@ hide: {: data-advanced="true" } [**`Pythonic patterns`**](style.md#pythonic-patterns): + [`mutable defaults`](style.md#mutable-default-arguments) [`is None`](style.md#is-none-instead-of-none) [`truthy checks`](style.md#truthy-checks) diff --git a/docs/style.md b/docs/style.md index 22ecc33..fe06e6f 100644 --- a/docs/style.md +++ b/docs/style.md @@ -15,69 +15,6 @@ Code that works isn't automatically code that's easy to read and maintain.
-## Script style checklist - -**PEP 8 Formatting** - -- [ ] **[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 - { 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#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" } -- [ ] **[`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 - -**Structure** - -- [ ] **[Keep functions focused](functions.md#keep-functions-focused)** — does each function do just one job? - -**Polished UX** - -- [ ] **[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 -- [ ] **[Randomize messages](#randomize-messages)** — varied responses instead of the same output every run - -**Polished UI** - -- [ ] **[Escape sequences](#escape-sequences)** — `\n`, `\t`, and friends used instead of literal characters -- [ ] **[Color styling](#color-styling)** — an ANSI code and a reset instead of plain, uncolored text -- [ ] **[Multi-line strings](#multi-line-strings)** — a triple-quoted string instead of several chained `print()` calls -- [ ] **[Formatting variables](#formatting-variables)** — f-strings and format specs instead of manual string building -- [ ] **[Unicode symbols](#unicode-symbols)** — box-drawing, arrows, and checkmarks instead of plain ASCII -- [ ] **[Dividers](#dividers)** — a row of repeated characters instead of a full box, to separate sections of output -- [ ] **[Boxes](#boxes)** — a decorative box or bordered menu instead of a bare print statement -- [ ] **[Progress bars](#progress-bars)** — visible feedback during a delay instead of a silent pause - -
- -
- ## Linter tool A **linter** is a tool that scans your code and flags issues like [PEP 8](#pep-8-style-guide), Python's official style guide, and [Pythonic](#pythonic-patterns) idioms automatically. It reads your file, checks it against its rule set, and prints a report: one line per violation, giving the file, line number, a rule code, and a short message. @@ -115,7 +52,7 @@ Python runs styled and unstyled code identically, so following PEP 8 doesn't mak ### File order { data-advanced="true" } -A Python file conventionally follows the same layout, top to bottom.[^order-pep8] +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 @@ -306,6 +243,21 @@ 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: +### Mutable default arguments + +A default argument's value is created once, when the function is defined — not fresh on every call. A mutable default like a list or dict is quietly reused and built up across every call that doesn't pass its own, instead of starting empty each time. + +```python-ref +def add_sighting(species, log=[]): # the same list, reused on every call + log.append(species) + return log + +def add_sighting(species, log=None): # Pythonic — a fresh list every call + log = [] if log is None else log + log.append(species) + return log +``` + ### Truthy checks instead of `len(x) > 0` { #truthy-checks data-advanced="true" } Test a collection directly — a non-empty list is already truthy. @@ -332,7 +284,7 @@ for i, s in enumerate(species): # Pythonic — enumerate() hands back both ### `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. +Checking against `None` is a check of identity, not equality, so `is` is the correct tool — `==` usually happens to work too, but a class can override what `==` means, which makes this a real correctness risk and not just a style nit. ```python-ref length_ft = None @@ -347,13 +299,28 @@ if length_ft is None: # Pythonic — `is` is the correct tool for
+## Efficient code { data-advanced="true" } + +Correct code produces the right output. Efficient code does it without spending more time or memory than the problem needs. Two properties matter here, worth naming separately: + +- **Runtime** — how the amount of work grows as the input grows. +- **Space** — how much memory a program holds onto while it runs, independent of how long it takes. + +For a script working through a handful of snakes, the difference rarely shows up — a computer runs almost any approach fast enough to not notice. It shows up at scale: a full species inventory, thousands of logged sightings, a program that keeps running instead of finishing in a second. A list scanned item by item and a set looked up directly do the same job, but one keeps taking longer as the data grows and the other doesn't. + +The standard way to describe this is **Big O notation** — O(1) for constant time (the cost stays the same regardless of input size), O(n) for linear time (the cost grows in proportion to it), and so on for anything in between or beyond. The `perf` admonitions placed throughout this guide use that notation to flag the spots where Python offers more than one way to do something and one option holds up better as the input grows — a set instead of a list for membership checks, `.join()` instead of repeated string concatenation, an iterative rewrite instead of deep recursion. The pattern behind each one is worth recognizing on its own; the notation is just a precise, compact way to name it. + +
+ +
+ ## Polished UX -**UX** (user experience) here means how the script behaves — how it responds to what someone types. A validated input, a working menu, and a varied response all make it feel considered instead of accidental. +**UX** (user experience) is how a program interacts with the person running it and engages with them — including what it asks, how it reacts to their answer, and how it recovers when they get something wrong. ### Input validation -An `input()` is only as reliable as what it assumes the user will type. +An `input()` is only as reliable as what it assumes the user will type. Validating means re-asking on a bad or missing answer, instead of letting the program crash or continue on with garbage input. #### Wrong choice @@ -405,7 +372,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). +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. #### Simple input @@ -552,11 +519,11 @@ else: ## Polished UI -**UI** (user interface) here means how the script's output looks — the terminal text itself, creatively styled within its limitations: formatted output, Unicode framing, and animated progress. +**UI** (user interface) is how a program presents itself to the person running it. Just like an app or website, the terminal is an interface that can be designed within its limitations to create a more engaging and intuitive user experience. ### Escape sequences -An **escape sequence** is a backslash followed by a letter, standing in for a character that couldn't otherwise appear in the string. +An **escape sequence** is a backslash followed by a letter, standing in for a character that couldn't otherwise appear in the string — used instead of typing the literal character (an actual tab, an actual line break) directly into the source. | Escape | Prints | |---|---| @@ -601,9 +568,11 @@ Here are three ways to print the same four-line string: """) ``` +For anything longer than a line or two, the triple-quoted string is easiest to read and change later — it holds the whole layout in one block, instead of assembling it across several separate `print()` calls. + ### Formatting variables -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, and what plugs a typed-in name into the [banner](#banner)'s greeting. 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.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: 1. fill (padding character) 2. align (left, right, center, or pad between a sign and its digits) @@ -632,6 +601,8 @@ print(f""" ### Unicode symbols +Box-drawing characters, arrows, and checkmarks give output visual structure that plain ASCII can't — swapped in wherever a border, pointer, or status icon would otherwise just be a `-`, `>`, or `x`. + #### Original ASCII **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**. @@ -738,7 +709,7 @@ print(""" ### Progress bars -`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](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. ```python-ref import time @@ -800,7 +771,7 @@ The above character changes in place, so you see an animation cycling through th ### Color styling -Terminal text that has **color**, **bold**, **underlines**, and a **background color** can be styled by printing escape sequences around the string you would like to style. +Terminal text that has **color**, **bold**, **underlines**, and a **background color** can be styled by printing escape sequences around the string you would like to style, instead of leaving it plain — an ANSI code before it, and a reset code after so the styling doesn't leak into whatever prints next. #### Escape sequence structure diff --git a/docs/types.md b/docs/types.md index 7e44df1..e012643 100644 --- a/docs/types.md +++ b/docs/types.md @@ -1058,11 +1058,12 @@ venomous = False ### Going further { data-card-link="skip" } ??? note "Bool is a subclass of int" - `True` behaves like `1` and `False` behaves like `0` in arithmetic. + `True` behaves like `1` and `False` behaves like `0` in arithmetic — and since a `bool` is a valid index too, it can pick directly between two items in a tuple instead of writing an `if`/`else`. ```python-ref - isinstance(True, int) # True - True + True # 2 + isinstance(True, int) # True + True + True # 2 + ("no", "yes")[True] # "yes" ``` ??? run "Practice with booleans" From afc530e4824079af3cf2545fdb96526a9c162e7c Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Sat, 19 Sep 2026 22:36:54 -0700 Subject: [PATCH 2/5] add new efficient code section --- docs/index.md | 7 ++++++ docs/style.md | 70 +++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 64 insertions(+), 13 deletions(-) diff --git a/docs/index.md b/docs/index.md index 9400aaf..bad16b3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -562,6 +562,13 @@ hide: [`enumerate()`](style.md#enumerate-instead-of-range) {: data-advanced="true" } + [**`Efficient code`**](style.md#efficient-code) + [`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) + {: data-advanced="true" } + [**`Polished UX`**](style.md#polished-ux): [`input validation`](style.md#input-validation) [`menus`](style.md#menus) diff --git a/docs/style.md b/docs/style.md index fe06e6f..aaf584f 100644 --- a/docs/style.md +++ b/docs/style.md @@ -240,7 +240,7 @@ length_ft = 4.5 # too short # PEP 8 — two spaces before, one after There's no single tool that reliably flags all "unpythonic" code the way PEP 8 has a document to check against. The real habit is asking *"does Python already have a built-in way to do this?"* before writing a manual loop, counter, or flag — an instinct built over time to recognize the built-in pattern. -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. +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. A few of these a beginner tends to write out longhand before learning the built-in shortcut, roughly most to least common: ### Mutable default arguments @@ -258,7 +258,7 @@ def add_sighting(species, log=None): # Pythonic — a fresh list every call return log ``` -### Truthy checks instead of `len(x) > 0` { #truthy-checks data-advanced="true" } +### Truthy checks instead of len(x) > 0 { #truthy-checks data-advanced="true" } Test a collection directly — a non-empty list is already truthy. @@ -270,7 +270,7 @@ if species: # Pythonic — a non-empty list is already truthy print("found some") ``` -### `enumerate()` instead of `range(len(...))` { #enumerate-instead-of-range data-advanced="true" } +### enumerate() instead of range(len(...)) { #enumerate-instead-of-range data-advanced="true" } Loop with both the index and the item at once, instead of indexing into the list by hand. @@ -282,7 +282,7 @@ for i, s in enumerate(species): # Pythonic — enumerate() hands back both print(i, s) ``` -### `is None` instead of `== None` { #is-none-instead-of-none } +### 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 — `==` usually happens to work too, but a class can override what `==` means, which makes this a real correctness risk and not just a style nit. @@ -301,14 +301,55 @@ if length_ft is None: # Pythonic — `is` is the correct tool for ## Efficient code { data-advanced="true" } -Correct code produces the right output. Efficient code does it without spending more time or memory than the problem needs. Two properties matter here, worth naming separately: +Correct code produces the right output. -- **Runtime** — how the amount of work grows as the input grows. -- **Space** — how much memory a program holds onto while it runs, independent of how long it takes. +Efficient code does it **without spending more resources than the problem needs**, which becomes a significant issue once your number of variables or calculations start increasing to the thousands and beyond. -For a script working through a handful of snakes, the difference rarely shows up — a computer runs almost any approach fast enough to not notice. It shows up at scale: a full species inventory, thousands of logged sightings, a program that keeps running instead of finishing in a second. A list scanned item by item and a set looked up directly do the same job, but one keeps taking longer as the data grows and the other doesn't. +### Time and space { data-card-link="skip" } -The standard way to describe this is **Big O notation** — O(1) for constant time (the cost stays the same regardless of input size), O(n) for linear time (the cost grows in proportion to it), and so on for anything in between or beyond. The `perf` admonitions placed throughout this guide use that notation to flag the spots where Python offers more than one way to do something and one option holds up better as the input grows — a set instead of a list for membership checks, `.join()` instead of repeated string concatenation, an iterative rewrite instead of deep recursion. The pattern behind each one is worth recognizing on its own; the notation is just a precise, compact way to name it. +These are two **computational resources** to weigh while designing a program — not the only ones that exist, but the two that show up most in everyday Python code. + +| | Time | Space | +|---|---|---| +| **Definition** | Steps an operation takes. A faster computer still runs the same code quicker. | Extra memory an operation needs, beyond the input itself. | +| **You might not notice this on a small script because...** | Modern hardware can still run it fast enough not to notice. | The items can still easily fit in memory. | +| **Starts becoming an issue at scale because...** | A test list of 10 behaves nothing like a real dataset of 100,000, if the operation grows quadratically instead of linearly. | Holding several full copies of a 100,000-record dataset can exceed available memory. | +| **Risk if ignored** | Slows down or stops responding — and can cost $, since servers bill for processing time used. | Runs out of memory and crashes — and can cost $, since servers bill for memory used. | + +#### Big O notation + +Representing **O**rder of growth, the standard way to describe *how time and space grow*: + +- O(1) constant -> doesn't grow with n — the same extra space or steps no matter the input size +- O(n) or O(log n) or O(n log n) -> grows but stays manageable — double the input, and an O(n) operation takes about twice as long +- O(n²) or worse -> grows fast enough to become a bottleneck once n is large — double the input, and it takes four times as long + +#### Other considerations + +- **Constant factor** — has the same growth rate, but each individual step actually costs less time or memory to run — like two people crossing a room in the same number of steps, just one takes bigger, faster steps than the other. +- **Redundant work** — doing something twice that once would cover. +- **Amortized cost** — a single call is occasionally expensive (list `append()` resizing its underlying storage, say), but averaged across every call it makes over time, the cost still comes out cheap. + + +### Common optimizations { data-card-link="skip" } + +| 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 | +| [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 |
@@ -678,7 +719,7 @@ Copy and paste these Unicode characters into your print statements, or [Browse t ### Dividers -A row of repeated characters separates sections of output — simpler than drawing a full box. +A row of repeated characters separates sections of output. ```python print("survey results") @@ -687,7 +728,7 @@ print("=" * 40) ### Boxes -Combine the [box-drawing unicode symbols](#unicode-symbols) to emphasize output — a full frame instead of just a [divider](#dividers) line. +Combine the [box-drawing unicode symbols](#unicode-symbols) to emphasize output, these were designed for early programs. ```python print(""" @@ -747,9 +788,12 @@ print("█" * 10) █░░░░░░░░ ██░░░░░░░ ███░░░░░░ -... +████░░░░░ +█████░░░░ +██████░░░ +███████░░ +████████░ █████████ -██████████ ``` A fixed list of characters, indexed with `i % len(spinner)` so it wraps back to the start instead of running out, animates the same way — a spinner instead of a bar. From fa1774ad82df8abe5a5d3bd3a337a20472c9aa98 Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Sat, 19 Sep 2026 22:44:14 -0700 Subject: [PATCH 3/5] reorder linter section --- docs/index.md | 4 ++-- docs/style.md | 58 +++++++++++++++++++++++++-------------------------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/docs/index.md b/docs/index.md index bad16b3..b6095e7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -539,8 +539,6 @@ hide: Readable Python code, and polished UI. - [**`linter`**](style.md#linter-tool) - [**`PEP 8`**](style.md#pep-8-style-guide): [`blank lines`](style.md#blank-lines) [`docstrings`](style.md#docstrings) @@ -554,6 +552,8 @@ hide: [`quote style`](style.md#quote-style) {: data-advanced="true" } + [**`Linters and formatters`**](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) diff --git a/docs/style.md b/docs/style.md index aaf584f..39454f4 100644 --- a/docs/style.md +++ b/docs/style.md @@ -15,35 +15,6 @@ Code that works isn't automatically code that's easy to read and maintain.
-## Linter tool - -A **linter** is a tool that scans your code and flags issues like [PEP 8](#pep-8-style-guide), Python's official style guide, and [Pythonic](#pythonic-patterns) idioms automatically. It reads your file, checks it against its rule set, and prints a report: one line per violation, giving the file, line number, a rule code, and a short message. - -It can't catch a bug that only shows up when the code actually runs, since it never runs it. - -A **formatter** tool (either separate, or a combined linter+formatter), actually rewrites your file on its own fixing the errors. However, it can be helpful to manually fix the issues on your own, so you learn to write them correctly for next time. - -**Comparing different tools** - -| Tool | Type | Best for | -|------|------|----------| -| PyCharm's built-in inspections | Linter | No setup needed — catches most PEP 8 violations and several Pythonic issues automatically | -| Pylint | Linter | Comprehensive checks — catches complex logical errors, not just formatting | -| Ruff | Linter & Formatter | Speed — large projects or CI pipelines where Pylint's speed becomes noticeable | -| Black | Formatter | Eliminating style debates entirely — rewrites the file to a consistent style automatically, instead of just flagging issues | - -**Get started in your environment** - -| Environment | Installing third party tools | Using a linter | Using a formatter | -|-------------|-----------------------------------|-----------------|--------------------| -| PyCharm | `Settings > Plugins >` tool name, then restart | Built-in inspections run automatically, no setup needed; plugins do too, once installed. Underlines issues, hover for full message. Full issue list in `View > Tool Windows > Problems`. | `Code > Format Code` | -| VS Code | `View > Extensions >` tool name | Underlines issues, hover for full message. Full issue list in `View > Problems`. | Trigger via `Format Document`, or set it as the default formatter in `settings.json` | -| Outside of an IDE | Send in terminal: `pip install` [tool name] | print report in the terminal:
  • `pylint your_file.py`
  • `ruff check your_file.py`
| rewrite the file directly:
  • `black your_file.py`
  • `ruff format your_file.py`
| - -
- -
- ## PEP 8 style guide [**PEP 8** is Python's official style guide](https://peps.python.org/pep-0008/) — a document written by Python's own core developers covering formatting, naming, and organizing code. "PEP" stands for Python Enhancement Proposal. @@ -234,6 +205,35 @@ length_ft = 4.5 # too short # PEP 8 — two spaces before, one after
+## Linters and formatters + +A **linter** is a tool that scans your code and flags issues like [PEP 8](#pep-8-style-guide), Python's official style guide, and [Pythonic](#pythonic-patterns) idioms automatically. It reads your file, checks it against its rule set, and prints a report: one line per violation, giving the file, line number, a rule code, and a short message. + +It can't catch a bug that only shows up when the code actually runs, since it never runs it. + +A **formatter** tool (either separate, or a combined linter+formatter), actually rewrites your file on its own fixing the errors. However, it can be helpful to manually fix the issues on your own, so you learn to write them correctly for next time. + +**Comparing different tools** + +| Tool | Type | Best for | +|------|------|----------| +| PyCharm's built-in inspections | Linter | No setup needed — catches most PEP 8 violations and several Pythonic issues automatically | +| Pylint | Linter | Comprehensive checks — catches complex logical errors, not just formatting | +| Ruff | Linter & Formatter | Speed — large projects or CI pipelines where Pylint's speed becomes noticeable | +| Black | Formatter | Eliminating style debates entirely — rewrites the file to a consistent style automatically, instead of just flagging issues | + +**Get started in your environment** + +| Environment | Installing third party tools | Using a linter | Using a formatter | +|-------------|-----------------------------------|-----------------|--------------------| +| PyCharm | `Settings > Plugins >` tool name, then restart | Built-in inspections run automatically, no setup needed; plugins do too, once installed. Underlines issues, hover for full message. Full issue list in `View > Tool Windows > Problems`. | `Code > Format Code` | +| VS Code | `View > Extensions >` tool name | Underlines issues, hover for full message. Full issue list in `View > Problems`. | Trigger via `Format Document`, or set it as the default formatter in `settings.json` | +| Outside of an IDE | Send in terminal: `pip install` [tool name] | print report in the terminal:
  • `pylint your_file.py`
  • `ruff check your_file.py`
| rewrite the file directly:
  • `black your_file.py`
  • `ruff format your_file.py`
| + +
+ +
+ ## Pythonic patterns **Pythonic** code uses Python's own built-in features and standard patterns, instead of verbose work arounds. From a920efcc5a9b0ee210d65a949651f1bb7abb4bc9 Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Sat, 19 Sep 2026 23:20:42 -0700 Subject: [PATCH 4/5] add efficiency admontions --- STRUCTURE.md | 1 + docs/classes.md | 25 ++++++++++++++ docs/collections.md | 68 ++++++++++++++++++++++++++++++++++++++ docs/errors.md | 14 ++++++++ docs/files.md | 16 +++++++++ docs/functions.md | 16 +++++++++ docs/index.md | 4 +-- docs/libraries/numpy.md | 12 +++++++ docs/libraries/re.md | 10 ++++++ docs/style.md | 2 +- docs/stylesheets/extra.css | 62 ++++++++++++++++++++++++++++++++-- docs/types.md | 16 +++++++++ includes/glossary.md | 3 ++ tests/test_structure.py | 1 + 14 files changed, 245 insertions(+), 5 deletions(-) diff --git a/STRUCTURE.md b/STRUCTURE.md index e47b8c2..a30d898 100644 --- a/STRUCTURE.md +++ b/STRUCTURE.md @@ -249,6 +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. | | `!!! 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/classes.md b/docs/classes.md index 378ff39..d0b37e8 100644 --- a/docs/classes.md +++ b/docs/classes.md @@ -130,6 +130,31 @@ print(burmese.species) # "burmese" — a separate copy, not shared For a value every object should share instead of holding its own copy, see [class attributes](#class-attributes) below. +
+ +??? efficiency "For efficiency, use __slots__ when creating many instances" + | | Time | Space (n instances) | + |---|---|---| + | 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: + + ```python-ref + class Snake: + __slots__ = ("species", "length_ft") # only these attributes are allowed, no __dict__ + + def __init__(self, species, length_ft): + self.species = species + self.length_ft = length_ft + ``` + + 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. + +
+ ### Class attributes A class attribute is set directly in the class body, outside `__init__` — shared by every object built from that class, unlike an [instance attribute](#instance-attributes), which is a separate copy per object. Assigning to `object.attribute` always creates (or updates) an instance attribute, even if a class attribute of the same name exists — it doesn't change the shared value, just shadows it for that one object. diff --git a/docs/collections.md b/docs/collections.md index 0d6f363..7292b7c 100644 --- a/docs/collections.md +++ b/docs/collections.md @@ -443,6 +443,32 @@ class diagram panel 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. +
+ +??? efficiency "For efficiency, use append()/pop() instead of insert(0, x)/pop(0)" + | | Time | Space | + |---|---|---| + | `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. + + See [Efficiency](style.md#efficiency) for why this distinction matters. + +??? efficiency "For efficiency, sorted() copies the list; sort() doesn't" + | | Time | Space | + |---|---|---| + | `sort()` | O(n log n) | O(1) | + | `sorted()` | O(n log n) | O(n) | + + `sort()` rearranges the list **in place**, while `sorted()` builds and returns an entirely new one, so both copies sit in memory at once until the original is no longer needed. + + 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. + +
+
@@ -508,6 +534,20 @@ flowchart LR snake.get("weight_lbs", 0) # 0 — key is missing, so the default is returned instead of None ``` +
+ +??? efficiency "For efficiency, use .get() instead of checking in first" + | | Time | Space | + |---|---|---| + | `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. + + See [Efficiency](style.md#efficiency) for why this distinction matters. + +
+ ### Loop through a dictionary - Looping **directly** over a dictionary gives you its keys, one at a time — the loop runs once for every key in the dictionary, and on each pass the loop variable, *(i.e. `key`)* is set to the next key. @@ -659,6 +699,20 @@ flowchart LR snakes["burmese"]["length_ft"] # 16 ``` +
+ +??? efficiency "For efficiency, use a dict instead of a list to look up by key" + | | Time | Space | + |---|---|---| + | Dict `dict[key]` / `.get()` | O(1) | — | + | List of `(key, value)` tuples, searched by hand | O(n) | — | + + 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. + +
+ ### Going further { data-card-link="skip" } ??? run "Practice with dictionaries" @@ -1271,6 +1325,20 @@ These check a relationship between two sets and hand back a `bool`, rather than list(set(species)) # ["burmese", "ball", "boa"] — order not guaranteed ``` +
+ +??? efficiency "For efficiency, use a set instead of a list for membership checks" + | | Time | Space | + |---|---|---| + | List/tuple `in` | O(n) | — | + | Set/dict `in` | O(1) average | — | + + 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. + +
+ ### Going further { data-card-link="skip" } ??? run "Practice with sets" diff --git a/docs/errors.md b/docs/errors.md index b856044..f96ecf8 100644 --- a/docs/errors.md +++ b/docs/errors.md @@ -376,6 +376,20 @@ finally: print(f"found it: {length} ft") # runs only if try succeeded ``` +
+ +??? efficiency "For efficiency, use try/except when success is the common case" + | | Time | Space | + |---|---|---| + | `try` succeeds | O(1) | — | + | `try` fails | O(1) (larger constant, same class) | — | + + A `try` block that succeeds costs almost nothing — Python doesn't pay for exception handling until an exception is actually raised. When one is raised, unwinding to the matching `except` has real overhead, more than an `if` check would. That makes `try`/`except` (checking after — sometimes called EAFP, "easier to ask forgiveness than permission") cheap for something expected to usually succeed, like the `float()` conversion above, and comparatively expensive as a substitute for an `if` check on something that fails often — checking first (LBYL, "look before you leap") avoids paying for exceptions that are more the rule than the exception. + + See [Efficiency](style.md#efficiency) for why this distinction matters. + +
+ ??? run "Run a try/except example" A case where try/except is the right tool — converting a value that might not be a valid number: diff --git a/docs/files.md b/docs/files.md index cc5e979..8204514 100644 --- a/docs/files.md +++ b/docs/files.md @@ -212,6 +212,22 @@ with open("notes.txt", "r") as file: print(line.strip()) ``` +
+ +??? efficiency "For efficiency, loop over a file instead of reading it all at once" + | | Time | Space | + |---|---|---| + | `.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. + + 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. + +
+ #### 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. diff --git a/docs/functions.md b/docs/functions.md index 7c6827a..b97b097 100644 --- a/docs/functions.md +++ b/docs/functions.md @@ -556,6 +556,22 @@ Every recursive function needs two parts: print("liftoff") ``` +
+ +??? efficiency "For efficiency, use a loop instead of recursion to save memory" + | | Time | Space | + |---|---|---| + | Loop | O(n) | O(1) | + | Recursion, n levels deep | O(n) calls | O(n) stack | + + 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). + + See [Efficiency](style.md#efficiency) for why this distinction matters. + +
+ ??? run "Run a recursion example" All the examples above, combined into one script: diff --git a/docs/index.md b/docs/index.md index b6095e7..3ea4a2e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -552,7 +552,7 @@ hide: [`quote style`](style.md#quote-style) {: data-advanced="true" } - [**`Linters and formatters`**](style.md#linters-and-formatters) + [**`Linters, formatters`**](style.md#linters-and-formatters) [**`Pythonic patterns`**](style.md#pythonic-patterns): [`mutable defaults`](style.md#mutable-default-arguments) @@ -562,7 +562,7 @@ hide: [`enumerate()`](style.md#enumerate-instead-of-range) {: data-advanced="true" } - [**`Efficient code`**](style.md#efficient-code) + [**`Efficiency`**](style.md#efficiency) [`big O`](style.md#big-o-notation) [`common optimizations`](style.md#common-optimizations) [`time`](style.md#time-and-space) diff --git a/docs/libraries/numpy.md b/docs/libraries/numpy.md index 99ab63b..12f5f54 100644 --- a/docs/libraries/numpy.md +++ b/docs/libraries/numpy.md @@ -91,6 +91,18 @@ lengths_m = lengths_ft * 0.3048 print(lengths_m) ``` +??? efficiency "For efficiency, use vectorized operations instead of a Python loop" + | | Time | Space | + |---|---|---| + | 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. + + 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. + ### Aggregating an array Collapses an entire array down to a single summary number. `.mean()`, `.max()`, `.min()`, and `.sum()` — the same idea as Python's built-in `sum()` and `max()`, but computed directly on the array without converting it back to a list first. diff --git a/docs/libraries/re.md b/docs/libraries/re.md index b97c912..c3b7885 100644 --- a/docs/libraries/re.md +++ b/docs/libraries/re.md @@ -62,6 +62,16 @@ print(match.group()) length_pattern.search(note).group() # "12ft" ``` +??? efficiency "For efficiency, compile the pattern once with re.compile()" + | | Compilation cost (over a loop of n calls) | + |---|---| + | Recompiled every pass | O(n) | + | Compiled once, reused | O(1) | + + 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. + ??? run "Run a searching example" All the examples above, combined into one script: diff --git a/docs/style.md b/docs/style.md index 39454f4..fd18aa0 100644 --- a/docs/style.md +++ b/docs/style.md @@ -299,7 +299,7 @@ if length_ft is None: # Pythonic — `is` is the correct tool for
-## Efficient code { data-advanced="true" } +## Efficiency { data-advanced="true" } Correct code produces the right output. diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 327edfb..aa81a32 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -24,6 +24,9 @@ --pt-heading-h3: #33724C; --pt-ref-keyword: #825E25; --pt-danger: #933331; + /* Big O severity badges in style.md's "Efficient code" reference table — + amber middle ground between --pt-accent (cheap) and --pt-danger (expensive). */ + --pt-warn: #8A5A00; /* Runnable python blocks share the same light panel look as .pyodide-reference in light mode (not a dark terminal here — that's dark-mode-only, see the slate scheme below). */ @@ -54,6 +57,7 @@ --pt-heading-h3: #6FA783; --pt-ref-keyword: #C99B5A; --pt-danger: #CB7671; + --pt-warn: #D9A441; /* Runnable python blocks keep a dedicated dark terminal panel here (unlike the light scheme above, which now matches --pt-panel) — this @@ -377,16 +381,70 @@ input:checked + .md-consent__settings { mask-image: var(--md-admonition-icon--python); } +/* ---- Custom "efficiency" admonition for runtime/space asides (data-advanced only) ---- */ + +:root { + --md-admonition-icon--efficiency: url('data:image/svg+xml;charset=utf-8,'); +} + +.md-typeset .admonition.efficiency, +.md-typeset details.efficiency { + border-color: #8a5a00; +} + +.md-typeset .efficiency > .admonition-title, +.md-typeset .efficiency > summary { + background-color: rgba(138, 90, 0, 0.1); +} + +.md-typeset .efficiency > .admonition-title::before, +.md-typeset .efficiency > summary::before { + background-color: #8a5a00; + -webkit-mask-image: var(--md-admonition-icon--efficiency); + mask-image: var(--md-admonition-icon--efficiency); +} + +/* Big O severity badges — style.md's "Efficient code" reference table colors + each Time/Space cell by how expensive that growth rate actually is, so the + table reads at a glance instead of requiring the reader to parse notation + row by row. Three tiers only: cheap/moderate/expensive, not one color per + notation, since that's the actual decision a reader needs from the table. */ +.pt-bigo { + display: inline-block; + padding: 0.05em 0.5em; + border-radius: 0.25em; + font-family: "JetBrains Mono", monospace; + font-size: 0.85em; + font-weight: 600; + white-space: nowrap; +} + +.pt-bigo--good { + background-color: color-mix(in srgb, var(--pt-accent) 15%, transparent); + color: var(--pt-accent); +} + +.pt-bigo--ok { + background-color: color-mix(in srgb, var(--pt-warn) 15%, transparent); + color: var(--pt-warn); +} + +.pt-bigo--bad { + background-color: color-mix(in srgb, var(--pt-danger) 15%, transparent); + color: var(--pt-danger); +} + /* Material's own summary focus ring is gated behind a JS-added `.focus-visible` class (an old keydown-tracking polyfill it ships), not the native `:focus-visible` pseudo-class — `.md-typeset summary:not(.focus-visible)` unconditionally zeroes the outline until that class shows up. Restore a visible indicator via the native pseudo-class directly, same pattern used for the run button and card links elsewhere in this file, so keyboard focus - on our custom run/ai/python collapsible admonitions is never silently blank. */ + on our custom run/ai/python/efficiency collapsible admonitions is never silently blank. */ .md-typeset .run > summary:focus-visible, .md-typeset .ai > summary:focus-visible, -.md-typeset .python > summary:focus-visible { +.md-typeset .python > summary:focus-visible, +.md-typeset .efficiency > summary:focus-visible { outline: 0.15rem solid var(--pt-accent); outline-offset: 0.15rem; } diff --git a/docs/types.md b/docs/types.md index e012643..f376d0b 100644 --- a/docs/types.md +++ b/docs/types.md @@ -428,6 +428,22 @@ Strings use the same index and slice syntax as lists. `0` is the first character "-".join(["burmese", "python"]) # "burmese-python" ``` +
+ +??? efficiency "For efficiency, use .join() instead of += in a loop" + | | Time | Space | + |---|---|---| + | `+=` in a loop, n times | O(n²) total | O(n) | + | `.join()` | O(n) | O(n) | + + A string is immutable, so `name += "python"` doesn't grow the existing string — it builds an entirely new one and throws the old one away. Doing that once is nothing, but doing it on every pass of a loop means each pass copies everything accumulated so far, making the total cost O(n²) for n pieces. + + `.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. + +
+ - **`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. ```python-ref diff --git a/includes/glossary.md b/includes/glossary.md index d40ed23..6c8f869 100644 --- a/includes/glossary.md +++ b/includes/glossary.md @@ -51,6 +51,8 @@ *[Truthiness]: Whether a value counts as True or False when used somewhere a bool is expected, even if it isn't a bool itself *[hashable]: Can be used as a dict key or set member because it never changes after creation — most immutable types qualify, like int, float, str, bool, None, and tuple *[Hashable]: Can be used as a dict key or set member because it never changes after creation — most immutable types qualify, like int, float, str, bool, None, and tuple +*[hash]: A number computed from a value, used to find where it's stored in a dict or set almost instantly, instead of scanning for it +*[Hash]: A number computed from a value, used to find where it's stored in a dict or set almost instantly, instead of scanning for it *[queue]: A line of items processed in the order they arrive — the first one added is the first one handled *[Queue]: A line of items processed in the order they arrive — the first one added is the first one handled *[bug]: A mistake in your code that makes it do the wrong thing, whether or not Python actually notices and raises an error @@ -69,3 +71,4 @@ *[Lazily]: In a way that computes or produces a value only at the moment it's actually needed, instead of all at once ahead of time *[PascalCase]: Capitalizing each word with no separators (e.g. Snake, BallPython) — the naming convention for classes, unlike variables' snake_case *[UTF-8]: The encoding used to save a Python file by default — the scheme that turns each Unicode character into the actual bytes a computer stores and reads, and it can represent every character Unicode defines +*[Big O]: Notation describing how a cost grows as the input size grows, not the exact number of seconds or bytes diff --git a/tests/test_structure.py b/tests/test_structure.py index be49101..ba470ea 100644 --- a/tests/test_structure.py +++ b/tests/test_structure.py @@ -41,6 +41,7 @@ # a look. DOCUMENTED_ADMONITION_TYPES = { "run", "tip", "warning", "note", "info", "failure", "example", "success", "danger", "ai", + "efficiency", } ADMONITION_RE = re.compile(r"^\s*(\?\?\?|!!!)\s+(\w+)\s") From 39ea71bc22945a36e42372b2c51e58dca9d35341 Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Sat, 19 Sep 2026 23:35:47 -0700 Subject: [PATCH 5/5] add readme with examples of essentials vs advanced --- README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 79562bb..5f5e8b2 100644 --- a/README.md +++ b/README.md @@ -26,9 +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, not as a complete Python language reference, but as a visualization of my mental model of how Python works. +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: +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 @@ -123,6 +123,16 @@ A two-option [switch](docs/javascripts/essentials_toggle.js) that lets a reader 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. +Some examples of content that is hidden while in "Essentials" mode, while a student is first learning to program: + +- Collection types a beginner can often get by without (tuples, sets) +- OOP features past a basic class (method decorators, multiple inheritance, polymorphism, encapsulation, operator overloading, dataclasses, abstract base classes). OOP can already be a challenging topic, and they should first have a strong understanding of it before adding these features. +- Function features (type hints, positional-only/keyword-only parameters, recursion, decorators, generators) +- File-handling edge cases (seek and tell, the `"x"` create mode) +- Styling suggestions that aren't critical (file order, constants, quote style, indentation, comments, the truthy-check and `enumerate()` idioms) +- 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 + ## Theme ### Custom CSS