diff --git a/README.md b/README.md index 4aeedef..4750c6a 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,29 @@ plugins: Material has no built-in way to tailor a page to different readers. The plugin hides a marked heading together with its whole section and its table of contents entry. It switches to the nearest mode that shows the content when a link points to something hidden. It also collapses to icons or moves to its own row on narrow screens. It includes its own Playwright and axe-core tests. See the [plugin's README](https://github.com/luka-sherman/mkdocs-audience-toggle) for all options. This site's setup is under `audience_toggle` in `mkdocs.yml`, with color overrides in `extra.css`'s `#fcm-toggle` rule. +### [mkdocs-cheatsheet](https://pypi.org/project/mkdocs-cheatsheet/) + +Builds the homepage's quick-reference grid from headings flagged across the site. Each page becomes a card: its title, a description, and a bold line per flagged `##` heading followed by links to the flagged headings under it. The grid on this homepage used to be written by hand, with every link typed out; renaming a heading silently broke its link. Now each heading carries its own flag, so the grid is rebuilt from the pages on every build. + +```bash +pip install mkdocs-cheatsheet +``` + +```yaml +plugins: + - cheatsheet: + button: true +``` + +```markdown + + +## Lists { cs="lists, item" } +#### Add item { cs="append, extend, insert" } +``` + +A section with its own cheatsheet page, like Libraries here, shows up on the homepage as title-and-description cards only. The optional header button replaces the logo with a "Cheatsheet" link home. It includes its own Playwright and axe-core tests. See the [plugin's README](https://github.com/luka-sherman/mkdocs-cheatsheet) for all options. This site's setup is under `cheatsheet` in `mkdocs.yml`, with the flag rules in STRUCTURE.md's "Cheatsheet flags" section. + ## Theme ### Custom CSS @@ -207,11 +230,11 @@ I found myself writing so much content for this, and needing to jump between dif end with a period, numbered walkthroughs start at `0.`, short (1–2 word) subheadings because `toc.integrate` mirrors them verbatim into the sidebar. - **Where information goes** — the decision rules for heading level vs. admonition vs. glossary - entry vs. footnote, with a table of which `??? type` to use for what, plus how the homepage - keyword deep-links in `index.md` have to cover every heading. + entry vs. footnote, with a table of which `??? type` to use for what, plus how headings are flagged + for the homepage cheatsheet. The mechanically-checkable subset of these rules (heading case, list-start number, admonition -types, `python-ref` comment format, homepage link coverage, clean `mkdocs build`) is enforced +types, `python-ref` comment format, clean `mkdocs build`) is enforced by `tests/test_structure.py`; the rest need editorial judgment. ## Running locally diff --git a/STRUCTURE.md b/STRUCTURE.md index 76d1727..1344f3b 100644 --- a/STRUCTURE.md +++ b/STRUCTURE.md @@ -143,85 +143,44 @@ staying inline.** 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`) - -Each card in `index.md`'s "What's inside" grid ends with a row of `` [`keyword`](page.md#anchor) `` -links — one per concept the page teaches, so a reader can jump straight to the specific thing -they're after instead of landing on the page and hunting. Library pages (`libraries/*.md`) are -the exception: their keyword links live only on their card in `libraries/index.md`'s own grid, -not on the main `index.md` — the top-level cards link to `libraries/.md` as a whole, -without a per-heading keyword row. `tests/test_homepage_keyword_links_cover_all_headings` checks -each library subpage's headings against `libraries/index.md` instead of `index.md` for exactly -this reason. - -- **Coverage — every `##` and `###` heading needs an entry.** Not just "the topic is - represented somewhere nearby" — each heading gets its own link, using its own anchor. A page - with 5 `##` sections needs at least 5 entries. If two headings share slug text (e.g. - `collections.md`'s three "Access items" sections), MkDocs disambiguates the anchor with a - suffix (`#access-items`, `#access-items_1`, ...) — confirm the real slug in the built HTML - (`grep -n 'id="' site//index.html`) rather than guessing, since the suffix isn't - predictable from the heading text alone. -- **Also include concrete Python syntax the page teaches, even without its own heading** — a - method or function genuinely explained in prose (e.g. `dict.get()`, explained inline under - Dictionaries' "Access items" on `collections.md`) is exactly the kind of thing a reader - searches for by name. Link it to the heading whose content covers it. - - **Exception: content inside a `???` admonition has no anchor of its own** (admonitions - aren't headings — see "Admonitions" below), so syntax explained only inside one (e.g. - `sort()` inside collections.md's "Sort lists" tip, `zip()` inside loops.md's tip) can't be - linked precisely. Link to the nearest real heading above it instead and accept the - imprecision (e.g. `.sort()` → `collections.md#loop-lists`, the section the tip sits inside) - — never invent an anchor that doesn't exist. If nothing precedes it (e.g. `type()` / - `isinstance()` sit in a tip before types.md's first `##`), link the bare page with no - fragment rather than a made-up one. - - Don't link *every* method mentioned in passing — only ones a page is actually teaching. - `print()` reappearing as an example call on `errors.md` or `style.md` isn't being taught - there (that's `foundations.md`'s job); only that page's own card should link it. -- **Skip purely narrative/descriptive subheadings** — "What do you see when a program runs?", - "How do variables work?", "Structure of a print() statement" name a *question*, not a - reusable keyword. If a heading doesn't name a concrete concept or piece of syntax, it doesn't - need a card entry even though it still needs to exist as a heading per the rules above... to - be clear, the heading itself is still fine on the page; it just doesn't earn a homepage link. - Mark it `{ data-card-link="skip" }` (attr_list, appended to the heading line) so - `tests/test_homepage_keyword_links_cover_all_headings` knows the omission is deliberate - rather than flagging it as a gap. Any other heading just needs *a* link to its anchor — the - test doesn't check the link's text, so renaming an entry (or the heading) is a manual concern. -- **Bold `##` entries stay in page order; their plain children are alphabetized.** Each `##` - heading gets its own bold entry (e.g. `` [**`def`**](functions.md#defining-a-function) ``), - and those bold entries keep the page's own top-to-bottom heading order — don't reshuffle them. - The flat list of plain (non-bold) links under a bold entry — its `###`/`####` children, plus - any bare-syntax entries with no heading of their own — sorts alphabetically by link text - (case-insensitive), not by importance or page position. Symbols sort before letters (plain - ASCII order), so a line like `` [`+= -= *= /=`] `` lands ahead of `` [`abs`] ``. -- **Verify with a real build, not by eye** — `mkdocs build` prints a `WARNING` for every - anchor/link it can't resolve; treat a clean build as the actual pass/fail check for this list, - since hand-checked slugs are easy to get subtly wrong (trailing punctuation, duplicate-heading - suffixes, etc). +### Cheatsheet flags (`index.md`, `libraries/index.md`) + +The card grids on `index.md` and `libraries/index.md` are generated by the +[mkdocs-cheatsheet](https://github.com/luka-sherman/mkdocs-cheatsheet) plugin from flags on each +content page's headings. Neither index file lists links by hand; each holds a +`` placeholder. `libraries/index.md` has its own placeholder, so the homepage +shows library pages as title-and-description cards only, and their links appear only on +`libraries/index.md`. + +- **Flag syntax.** Append the marker inside the heading's attr_list braces: + `## Integers { cs="integers" }`. `{ cs }` uses the heading text. Several comma-separated labels + each become a link to the same heading: `#### Add item { cs="append, extend, insert" }`. +- **What each level becomes.** Labels on the `#` title show at the top of the card and link to the + top of the page (e.g. `isinstance`, `type`). A flagged `##` starts a bold "label:" line; its + extra labels become links in that line. Flagged `###`/`####` headings become the links under + the nearest `##`. A `##` line only shows its bold label when the `##` itself is flagged. +- **What to flag.** Flag headings that name a concrete concept or piece of syntax a reader would + look up by name. Leave narrative headings ("What do you see when a program runs?", "Going + further") unflagged. Syntax taught in prose without its own heading (e.g. `dict.get()`) goes on + the nearest heading whose content covers it, as an extra label. Only flag what a page actually + teaches; `print()` appearing as an example on `errors.md` isn't taught there. +- **Order.** `##` lines keep page order. Links within a line sort alphabetically + (`sort: alphabetical` in `mkdocs.yml`), symbols before letters. +- **Card text.** Each page's front matter sets `cheatsheet_description` (falling back to + `description`), plus `cheatsheet_title`, `cheatsheet_icon` or `cheatsheet_title_suffix` (the + built-in/third-party badge on library cards) where the defaults don't fit. +- **Essentials mode.** A flagged heading that carries `data-fcm-hide="essentials"` passes it to + its cheatsheet line or link, so the audience toggle hides both together. A whole card is + hidden with `cheatsheet_attrs: {data-fcm-hide: essentials}` in front matter. +- **Verify with a real build.** A renamed heading moves its flag with it, so the cheatsheet can't + go stale, but `mkdocs build` still prints a `WARNING` for any broken link elsewhere. - **Marking content "advanced" for the Essentials/Advanced toggle** — the header's segmented - control (both labels always visible, on every page) is provided by the - `mkdocs-audience-toggle` plugin (configured under `plugins:` in `mkdocs.yml`; see - CLAUDE.md's "Planned extraction" section) and hides content marked `data-fcm-hide="essentials"`, at - one of two granularities. Each spot that should hide is marked directly, in its own markdown - source — there's no derived/shared list, so a new advanced entry needs tagging in every place - it should disappear from: - - **A homepage keyword-link row** — the bolded keyword plus its row of related links (e.g. - `functions.md#decorators` or `collections.md#sets`, in `index.md`) — append - `{: data-fcm-hide="essentials" }` on its own line directly after the row, at the same indentation, - with no blank line before it (attr_list attaches it to that paragraph, which the plugin then - hides directly). - - **The matching heading on the actual content page** — e.g. `functions.md`'s - `## Decorators { data-fcm-hide="essentials" }` — append `{ data-fcm-hide="essentials" }` directly on - the heading line (same attr_list convention as `data-card-link="skip"` above). This hides that - heading, everything up to the next heading of the same or higher level, and its - integrated-TOC sidebar entry, on that page specifically. Tag the homepage row and the - content-page heading independently — the plugin doesn't infer one from the other, by design - (simpler and more robust than deriving a map at runtime). - - **A whole homepage card** (e.g. the OpenCV card) — append `{: data-fcm-hide="essentials" }` the - same way, right after the card's first paragraph (the icon + title link, e.g. - `[__OpenCV__](...)`) — attr_list can only attach it there, not to the enclosing `
  • `, so the - plugin alone would only hide that one line. `html[data-fcm-mode="essentials"] .grid.cards > ul - > li:has(> p[data-fcm-hide~="essentials"])` in `extra.css` walks up from that paragraph to hide - the whole enclosing `
  • ` too. This one has no content-page equivalent — it marks a whole - linked page, not a section within one, so there's nothing on that page itself to hide. + control is provided by the `mkdocs-audience-toggle` plugin (configured under `plugins:` in + `mkdocs.yml`) and hides content marked `data-fcm-hide="essentials"`. On a content page, append + `{ data-fcm-hide="essentials" }` to the heading line (e.g. `functions.md`'s + `## Decorators { data-fcm-hide="essentials" }`). This hides that heading, everything up to the + next heading of the same or higher level, and its sidebar entry. The cheatsheet picks the + marker up from the heading, so there's no second place to tag. Which spots to mark, and what counts as advanced/niche vs. core, is a per-editor judgment call — there's no test enforcing it either way. See the `mkdocs-audience-toggle` plugin's own README for the toggle mechanism itself. A page can still show a *link* to a hidden section (e.g. diff --git a/docs/about.md b/docs/about.md index 99c329f..ec421f5 100644 --- a/docs/about.md +++ b/docs/about.md @@ -22,11 +22,11 @@ Python is often the fastest language to write *correct* code in, even though it' ## Why I built this -My name's Luka, I'm a software engineer and Intro Python teacher. +My name's Luka, I'm a software engineer and Intro Python teacher. -*Python Field Guide* is a free, in-browser reference — most code blocks are editable and runnable directly on the page. It started as a few quick-reference explanations for students working on their first programs, and evolved into this site. +*Python Field Guide* is a free, in-browser reference — most code blocks are editable and runnable directly on the page. It started as a few quick-reference explanations for students working on their first programs, and evolved into this site. -I couldn't find a site my students would consistently use that had: +I couldn't find a site my students would consistently use that had: - simple explanations for beginners without technical jargon - no advanced topics that intimidate or overwhelm beginners @@ -37,13 +37,13 @@ I couldn't find a site my students would consistently use that had: It's built for learners — self-taught, students in an intro course, or anyone who wants one combined reference to work through start to finish, instead of a scattered pile of search results. -I'm hoping this can be a helpful cheatsheet for others to quickly reference syntax and structures. +I'm hoping this can be a helpful cheatsheet for others to quickly reference syntax and structures.
    -## About me +## About me I build software and spend a lot of time thinking about the small interaction details that decide whether something actually gets used or just abandoned — I've always liked designing and building things that solve a need. @@ -74,11 +74,11 @@ For more advanced Python: ## Helpful feedback -Spotted a mistake, or want to see something added? Let me know! +Spotted a mistake, or want to see something added? Let me know! -This *isn't* meant to be a comprehensive Python guide, it's just my self-published notes. +This *isn't* meant to be a comprehensive Python guide, it's just my self-published notes. -**Please be nice, I'm just one human out here doing my best.** +**Please be nice, I'm just one human out here doing my best.**
    diff --git a/docs/flow/conditionals.md b/docs/flow/conditionals.md index 2d28ac0..c5b74e0 100644 --- a/docs/flow/conditionals.md +++ b/docs/flow/conditionals.md @@ -1,4 +1,5 @@ --- +cheatsheet_description: Decision points that run code only if a condition is met. description: >- Python conditionals explained with runnable examples: if/elif/else, match/case, and boolean logic for branching program flow. @@ -8,9 +9,9 @@ description: >-
    -A **conditional** lets a program make decisions by running a **block** of code only when a [condition](#boolean-expressions) is `True`. +A **conditional** lets a program make decisions by running a **block** of code only when a [condition](#boolean-expressions) is `True`. -The condition ends with a colon `:`, and the block is the lines indented underneath it, treated as a single unit. +The condition ends with a colon `:`, and the block is the lines indented underneath it, treated as a single unit. **There are two types of conditional statements:** @@ -27,13 +28,13 @@ The condition ends with a colon `:`, and the block is the lines indented underne
    -## If / elif / else +## If / elif / else { cs="if\, elif\, else" } -A chain of `if`, `elif`, and `else`: +A chain of `if`, `elif`, and `else`: 1. Checks a series of [conditions](#boolean-expressions) in order 2. Runs the indented block under the first one that's `True` -3. Then exits the whole chain without checking any conditions below it. +3. Then exits the whole chain without checking any conditions below it. **if:** @@ -52,13 +53,13 @@ Optional, and always comes last if present. It has no condition of its own — i ```python-ref length = 12 -if length > 10: # always starts with the if +if length > 10: # always starts with the if print("that's a big snake") elif length > 7: # then any number (or none) of elifs print("that's a medium snake") elif length > 4: print("that's a small snake") -else: # lastly comes one optional else +else: # lastly comes one optional else print("that's a tiny snake") ``` @@ -93,7 +94,7 @@ else: # lastly comes one optional else

    FIG: the if/elif/else decision path

    -### Boolean expressions +### Boolean expressions { cs="boolean expressions" } A boolean expression is needed for every `if`/`elif`. @@ -106,7 +107,7 @@ elif [boolean expression]: A **boolean expression** is a boolean value (`True` or `False`) or anything that produces one, and is treated as the **condition** that must be `True` in order to run a block of code. -A comparison looks different depending on the type of value being checked, as shown below. All of these comparisons result in a `True` or `False` boolean expression. +A comparison looks different depending on the type of value being checked, as shown below. All of these comparisons result in a `True` or `False` boolean expression. !!! example "Comparisons by type" @@ -327,7 +328,7 @@ A comparison looks different depending on the type of value being checked, as sh print("snake in dict is over 2 ft") ``` -### Logical operators +### Logical operators { cs="and\, or\, not" } Logical operators `not`, `and`, `or` let a single `if` combine boolean expressions to create more complex conditions. @@ -350,7 +351,7 @@ A and B here are [boolean expressions](#boolean-expressions). **Order of operations:** When several logical operators appear together, Python evaluates `not` first, then `and`, then `or`. Even when parentheses aren't required, they often make the condition much easier to read. -### Going further { data-card-link="skip" } +### Going further ??? tip "Nested if" Checks a second condition only after the first is `True`. An `if` can contain another `if`, checked only once the outer condition is already `True` — each level of nesting adds another decision. If both conditions are simple, combining them with [`and`](#logical-operators) is usually clearer than nesting. @@ -556,7 +557,7 @@ A and B here are [boolean expressions](#boolean-expressions).
    -## Match / case +## Match / case { cs="match\, case" } A `match` statement compares one value against several `case` options and runs the code for the first matching `case`. @@ -621,7 +622,7 @@ match species: print("not a hatchling") ``` -### Match multiple values with | +### Match multiple values with | { cs="match with |" } Lets one `case` match several possible values using `|`, so you don't need a separate `case` for each one. @@ -633,7 +634,7 @@ match species: print("other") ``` -### Default value _ +### Default value _ { cs="_ wildcard" } Runs a block of code if no `case` matched — either discarding the value with `_`, or capturing it into a variable. @@ -653,7 +654,7 @@ match species: If you want the code to still run a block of code even if no specific `case` matched, there are two ways to add a default value at the end that will match anything. A default value goes last — without one, a value matching no cases would not run any block of code. **Option 1** (`_`) throws the matched value away; **Option 2** (giving it a variable name, like `n`) saves it so the block can use it. -### case + if +### case + if { cs } Only run the block of code if there's a `case` match *and* the `if` condition is also `True`. Adding `if [condition]` after a pattern turns it into a guard — the branch only runs if the pattern matches *and* the condition is `True`. If the guard is `False`, Python moves on to the next `case` even though the pattern itself matched. @@ -666,7 +667,7 @@ match length: print("small") ``` -### Unpacking a tuple +### Unpacking a tuple { cs="unpacking" } A `case` can pull a tuple apart into named pieces *while also* checking its shape or specific values. @@ -685,7 +686,7 @@ match snake: A `match` can pick a different `case` depending on the tuple's length or the value in a specific position, while *still* unpacking the rest into names — all in one step, as shown above. Compare with regular assignment (`length, species = snake`), which always unpacks the same way, would crash on a 1- or 3-item tuple, and can't pick a different case based on species. -### Going further { data-card-link="skip" } +### Going further ??? run "Run a match/case example" All the examples above, combined into one script: @@ -768,11 +769,11 @@ A `match` can pick a different `case` depending on the tuple's length or the val
    -## Control flow statements +## Control flow statements { cs="control flow" } `break` and `continue` are loop-control keywords, not conditional ones, but they almost always appear inside a conditional — checking a condition, then stopping the loop early (`break`) or skipping straight to the next pass (`continue`). They work the same way whether checked with `if`/`elif` or `match`/`case`, since neither creates its own loop scope — both just pass straight through to whatever loop contains them. Covered fully, with more examples, on the [Loops](loops.md#control-flow-statements) page. -### Break +### Break { cs="break" } Exits the loop immediately, skipping everything left in it. Nothing after it runs, and anything left in the sequence (or any remaining passes of the condition) is skipped entirely. @@ -797,7 +798,7 @@ for s in species: print(s) ``` -### Continue +### Continue { cs="continue" } Skips just the current pass, then keeps looping. The rest of the loop body doesn't run for that item, but the loop itself keeps going from the next item or the next check of the condition. @@ -822,7 +823,7 @@ for s in species: print(s) ``` -### Going further { data-card-link="skip" } +### Going further { cs="pass" } ??? tip "pass placeholder" Temporarily fill an empty block when you're not ready to write the inside code yet. Python doesn't allow an empty block after a colon. `pass` does nothing, but acts as a placeholder until you're ready to add code so that the empty block won't cause a syntax error in the meantime — works the same way after `if`/`elif`/`else` and `case` alike. diff --git a/docs/flow/loops.md b/docs/flow/loops.md index 82ae87e..efc26f6 100644 --- a/docs/flow/loops.md +++ b/docs/flow/loops.md @@ -1,4 +1,5 @@ --- +cheatsheet_description: Repeat a block of code multiple times. description: >- Python's for and while loops explained with runnable examples: iterating collections, break/continue, range(), enumerate(), and common patterns. @@ -8,7 +9,7 @@ description: >-
    -A **loop** repeats a block of code multiple times. +A **loop** repeats a block of code multiple times.
    @@ -50,9 +51,9 @@ A **loop** repeats a block of code multiple times.
    -## For loops +## For loops { cs="for" } -A `for` loop goes through an **iterable** (something that contains multiple values) one value at a time, assigning each value to `loop_variable` as it goes. The types of iterables are: +A `for` loop goes through an **iterable** (something that contains multiple values) one value at a time, assigning each value to `loop_variable` as it goes. The types of iterables are: | Iterable | Loop variable | Use it for | |---|---|---| @@ -61,11 +62,11 @@ A `for` loop goes through an **iterable** (something that contains multiple valu See [common patterns](#common-patterns) for [accumulating](#accumulator) something new during a loop, or [counting](#counter), as you loop. -You can use [control flow statements](#control-flow-statements) to [break](#break) a loop early or [continue](#continue) ahead to the next iteration as needed. +You can use [control flow statements](#control-flow-statements) to [break](#break) a loop early or [continue](#continue) ahead to the next iteration as needed. -### Loop a certain number of times +### Loop a certain number of times { cs="loop a set number of times" } -#### iterable = range() +#### iterable = range() { cs="range" } - `range()` generates a sequence of numbers to loop over. - it will run the block of code once for each number in the sequence @@ -87,7 +88,7 @@ You can use [control flow statements](#control-flow-statements) to [break](#brea | 2 | `start`, `stop` | `step = 1`| | 3 | `start`, `stop`, `step` | - | -**range(stop)** +**range(stop)** ```python for i in range(5): @@ -223,7 +224,7 @@ Naming the variable in a `range()` loop comes down to one of three choices: print("hiss") ``` -### Loop through a collection +### Loop through a collection { cs="loop through a collection" } #### iterable = collection @@ -303,7 +304,7 @@ for snake in snakes: print(snake) ``` -#### Loop with index and value +#### Loop with index and value { cs="enumerate, zip" } `enumerate()` hands you both the index and the value on every pass. It's the usual alternative to looping over `range(len(species))` when you need the index but still want direct access to each item. @@ -314,7 +315,7 @@ for i, s in enumerate(species): print(i, s) ``` -#### Loop in reverse +#### Loop in reverse { cs="reversed" } `reversed()` steps through a collection back to front, without needing to build a reversed copy first. Works on anything with a fixed order — list, tuple, string, `range()` — but not on a `set`, since it has no order to reverse. @@ -325,7 +326,7 @@ for s in reversed(species): print(s) # blood ball rock burmese ``` -### Going further { data-card-link="skip" } +### Going further ??? warning "Modifying a list while looping over it" Adding to or removing from a list while a `for` loop is walking over it shifts every item after the change into a different position — the loop keeps advancing by index, so it silently skips over whatever slid into the spot it already passed. @@ -382,7 +383,7 @@ for s in reversed(species):
    -## While loops +## While loops { cs="while" } A `while` loop repeats its body for as long as a condition stays `True`, checked again before every pass — the right tool when you don't know ahead of time how many passes you'll need, unlike a `for` loop's fixed number of items. That condition can be any boolean expression, watching for something to happen rather than counting toward it. @@ -394,7 +395,7 @@ while not handled: handled = True ``` -### Using a flag +### Using a flag { cs="flag" } A **flag** is a boolean variable, starting `True` or `False`, that gets flipped when something happens — used as the condition to end the loop based on an event rather than a pass count. @@ -413,7 +414,7 @@ print(found) # True print(i) # 3 — stopped as soon as "ball" was found ``` -### Sentinel +### Sentinel { cs="sentinel" } A **sentinel** is a specific stop-value you watch for, rather than a plain True/False flag — the loop keeps running until it sees that exact value. A common use is reading input until the user signals they're done. @@ -426,7 +427,7 @@ while species != "quit": print(f"logged: {species}") ``` -### Counter and flag names +### Counter and flag names { cs="counter and flag names" } A `while` loop doesn't create a loop variable automatically the way `for` does — whatever's driving the condition is a variable you declare and update yourself, so naming it clearly matters just as much. @@ -474,7 +475,7 @@ while not handled: print(i) ``` -### Boolean expressions +### Boolean expressions { cs="boolean expressions" } A boolean expression is needed for every `while` condition. @@ -485,7 +486,7 @@ while [boolean expression]: A **boolean expression** is a boolean value (`True` or `False`) or anything that produces one, and is treated as the **condition** that must be `True` in order to run a block of code. -A comparison looks different depending on the type of value being checked, as shown below. All of these comparisons result in a `True` or `False` boolean expression. +A comparison looks different depending on the type of value being checked, as shown below. All of these comparisons result in a `True` or `False` boolean expression. !!! example "Comparisons by type" @@ -706,7 +707,7 @@ A comparison looks different depending on the type of value being checked, as sh print("snake in dict is over 2 ft") ``` -### Logical operators +### Logical operators { cs="and, not, or" } Logical operators `not`, `and`, `or` let a single `while` condition combine boolean expressions to create more complex conditions. @@ -733,11 +734,11 @@ A and B here are [boolean expressions](#boolean-expressions).
    -## Common patterns +## Common patterns { cs="common patterns" } A few variable patterns show up across both `for` and `while` loops, tracking something as the loop runs rather than controlling it directly. -### Accumulator +### Accumulator { cs="accumulator" } An **accumulator** builds up a result across passes — summing, concatenating, or collecting values — instead of just tracking whether or how many times the loop has run. Initialize it before the loop, then update it inside the body each pass. @@ -769,7 +770,7 @@ print(total) # 25.0 print(results) # ["BURMESE", "ROCK", "BALL", "BLOOD"] ``` -### Counter +### Counter { cs="counter" } A **counter** tracks how many times a loop has run, or how many items met some condition — counting up or down, instead of accumulating a result. It follows the same three steps as an accumulator: initialize it before the loop, check or use it, and update it inside the body. @@ -789,7 +790,7 @@ while attempts > 0: print("out of attempts") ``` -### Nested loops +### Nested loops { cs="nested loops" } A loop can contain another loop — any combination of `for` and `while` works, not just two of the same kind. Useful when each item in the outer collection has its own inner collection to go through, like a list of lists. The inner loop runs all the way through for every single pass of the outer one. @@ -804,7 +805,7 @@ for species, tags in species_tags.items(): print(species, tag) ``` -### Going further { data-card-link="skip" } +### Going further ??? run "Run a common patterns example" All the examples above, combined into one script: @@ -859,7 +860,7 @@ for species, tags in species_tags.items():
    -## Control flow statements +## Control flow statements { cs="control flow" } A `for` loop and a `while` loop can both be redirected mid-run — cut short, skipped ahead by one pass, or wrapped up with a bit of code that only runs if nothing interrupted them. These keywords work identically in either loop type. @@ -872,7 +873,7 @@ for s in species: print(s) ``` -### Break +### Break { cs="break" } Exits the loop immediately, skipping everything left in it. Nothing after it runs, and anything left in the sequence (or any remaining passes of the condition) is skipped entirely. @@ -890,7 +891,7 @@ while count < 5: count += 1 # 0 1 2 ``` -### Continue +### Continue { cs="continue" } Skips just the current pass, then keeps looping. The rest of the loop body doesn't run for that item, but the loop itself keeps going from the next item or the next check of the condition. @@ -908,7 +909,7 @@ while count < 5: print(count) # 1 2 4 5 ``` -### Else +### Else { cs="else" } Runs once the loop finishes on its own — skipped entirely if `break` cut it short. Both `for` and `while` can end with an `else` block. @@ -926,7 +927,7 @@ else: print("done") # 0 1 2 done ``` -### Going further { data-card-link="skip" } +### Going further { cs="pass" } ??? tip "pass placeholder" Temporarily fill an empty loop body when you're not ready to write the inside code yet. Python doesn't allow an empty block after a colon. `pass` does nothing, but acts as a placeholder until you're ready to add code so that the empty block won't cause a syntax error in the meantime. Covered in more detail on the [Conditionals](conditionals.md#if-elif-else) page. diff --git a/docs/index.md b/docs/index.md index 528b268..91813b4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -82,710 +82,8 @@ hide: style AI fill:#a33f3f1a,stroke:#a33f3f,color:#a33f3f ``` -
    + -
    -#### Get started { .pt-homepage-heading } +# Add-On Libraries { .library-grid-heading } -
    - -- :material-monitor:{ .lg .middle } [__Workspace Setup__](start/workspace.md) - - Write Python on your computer. - - [**`install`**](start/workspace.md#step-0-install-python): - [`download`](start/workspace.md#step-0-install-python) - [`version`](start/workspace.md#step-0-install-python) - - [**`code editors`**](start/workspace.md#step-1-pick-an-application-to-write-code-in): - [`IDLE`](start/workspace.md#step-1-pick-an-application-to-write-code-in) - [`Pycharm`](start/workspace.md#step-1-pick-an-application-to-write-code-in) - [`Thonny`](start/workspace.md#step-1-pick-an-application-to-write-code-in) - [`VS Code`](start/workspace.md#step-1-pick-an-application-to-write-code-in) - - [**`how to write and run .py file`**](start/workspace.md#step-2-write-and-run-a-python-file): - [`file naming`](start/workspace.md#step-2-write-and-run-a-python-file) - - [**`Terminal`**](start/workspace.md#using-the-terminal): - [`cd`](start/workspace.md#using-the-terminal) - [`ls`](start/workspace.md#using-the-terminal) - [`pwd`](start/workspace.md#using-the-terminal) - [`shortcuts`](start/workspace.md#using-the-terminal) - {: data-fcm-hide="essentials" } - - [**`virtual environments`**](start/workspace.md#virtual-environments): - [`activate`](start/workspace.md#virtual-environments) - [`pip`](start/workspace.md#virtual-environments) - [`requirements.txt`](start/workspace.md#virtual-environments) - [`venv`](start/workspace.md#virtual-environments) - {: data-fcm-hide="essentials" } - -- :material-cube-outline:{ .lg .middle } [__Foundations__](start/foundations.md) - - Storing, displaying, and inputting values. - - [**`variables`**](start/foundations.md#variables): - [`naming`](start/foundations.md#naming-variables) - [`printing`](start/foundations.md#printing-variables) - [`reassigning`](start/foundations.md#reassigning-a-variable) - [`types`](start/foundations.md#variables-and-types) - - [**`expressions and statements`**](start/foundations.md#expressions-and-statements) - - [**`print`**](start/foundations.md#print-function): - [`escape sequences`](start/foundations.md#escape-sequences) - - [**`input`**](start/foundations.md#input-function) - - [**`comments`**](start/foundations.md#comments): - [`"""`](start/foundations.md#multi-line-comments-with) - [`#`](start/foundations.md#single-line-comments-with) - [`FIXME`](start/foundations.md#single-line-comments-with) - [`TODO`](start/foundations.md#single-line-comments-with) - - [**`tips for getting started`**](start/foundations.md#tips-for-getting-started) - -
    -
    - -
    -#### Data types { .pt-homepage-heading } - -
    - -- :material-shape-outline:{ .lg .middle } [__Basics__](types/basics.md) - - Kinds of values, and what you can do with them. - - [`isinstance`](types/basics.md) - [`type`](types/basics.md) - - [**`integers`**](types/basics.md#integers): - [`+ - * / **`](types/basics.md#arithmetic) - [`+= -= *= /= //= %= **=`](types/basics.md#apply-arithmetic-to-a-variable) - [`// % divmod`](types/basics.md#floor-division-modulo) - [`abs`](types/basics.md#absolute-value) - [`boolean expressions`](types/basics.md#boolean-expressions) - [`int`](types/basics.md#convert) - - [**`floats`**](types/basics.md#floats): - [`+ - * / **`](types/basics.md#arithmetic_1) - [`+= -= *= /= //= %= **=`](types/basics.md#apply-arithmetic-to-a-variable_1) - [`// % divmod`](types/basics.md#floor-division-modulo_1) - [`abs`](types/basics.md#adjust) - [`boolean expressions`](types/basics.md#boolean-expressions_1) - [`float`](types/basics.md#convert_1) - [`round`](types/basics.md#adjust) - - [**`strings`**](types/basics.md#strings): - [`+ * += *=`](types/basics.md#combine) - [`boolean expressions`](types/basics.md#boolean-expressions_2) - [`capitalize`](types/basics.md#modify) - [`combine`](types/basics.md#combine) - [`count`](types/basics.md#search) - [`endswith`](types/basics.md#validate) - [`f-string`](types/basics.md#building-strings) - [`find`](types/basics.md#search) - [`format`](types/basics.md#building-strings) - [`format spec`](types/basics.md#building-strings) - [`in`](types/basics.md#search) - [`index`](types/basics.md#access-characters) - [`isalpha`](types/basics.md#validate) - [`isdigit`](types/basics.md#validate) - [`join`](types/basics.md#combine) - [`len`](types/basics.md#inspect) - [`lower`](types/basics.md#modify) - [`replace`](types/basics.md#modify) - [`slice`](types/basics.md#access-characters) - [`split`](types/basics.md#convert_2) - [`startswith`](types/basics.md#validate) - [`step`](types/basics.md#access-characters) - [`str`](types/basics.md#convert_2) - [`strip`](types/basics.md#modify) - [`title`](types/basics.md#modify) - [`upper`](types/basics.md#modify) - - [**`booleans`**](types/basics.md#booleans): - [`== != > < >= <=`](types/basics.md#boolean-expressions_3) - [`and`](types/basics.md#logical-operators) - [`in`](types/basics.md#boolean-expressions_3) - [`is`](types/basics.md#boolean-expressions_3) - [`not`](types/basics.md#logical-operators) - [`or`](types/basics.md#logical-operators) - - [**`None`**](types/basics.md#none): - [`boolean expressions`](types/basics.md#boolean-expressions_4) - [`is`](types/basics.md#check-for-none) - [`is not`](types/basics.md#check-for-none) - -- :material-basket-outline:{ .lg .middle } [__Collections__](types/collections.md) - - Multiple related values grouped into one container. - - [`isinstance`](types/collections.md) - [`type`](types/collections.md) - - [**`lists`**](types/collections.md#lists): - [`+`](types/collections.md#create) - [`append`](types/collections.md#add-item) - [`boolean expressions`](types/collections.md#boolean-expressions) - [`clear`](types/collections.md#remove-item) - [`comprehension`](types/collections.md#list-comprehension) - [`copy`](types/collections.md#create) - [`count`](types/collections.md#inspect) - [`create`](types/collections.md#create-a-list) - [`del`](types/collections.md#remove-item) - [`extend`](types/collections.md#add-item) - [`in`](types/collections.md#boolean-expressions) - [`index`](types/collections.md#create-a-list) - [`insert`](types/collections.md#add-item) - [`item`](types/collections.md#lists) - [`len`](types/collections.md#inspect) - [`list`](types/collections.md#create) - [`loop`](types/collections.md#loop-through-a-list) - [`max`](types/collections.md#arithmetic) - [`min`](types/collections.md#arithmetic) - [`pop`](types/collections.md#remove-item) - [`remove`](types/collections.md#remove-item) - [`reverse`](types/collections.md#sort) - [`slice`](types/collections.md#access-and-update-items) - [`sort`](types/collections.md#sort) - [`sorted`](types/collections.md#sort) - [`step`](types/collections.md#access-and-update-items) - [`sum`](types/collections.md#arithmetic) - - [**`dictionaries`**](types/collections.md#dictionaries): - [`access a value`](types/collections.md#access-a-value) - [`boolean expressions`](types/collections.md#boolean-expressions_1) - [`clear`](types/collections.md#remove_1) - [`copy`](types/collections.md#create_1) - [`del`](types/collections.md#remove_1) - [`dict`](types/collections.md#create_1) - [`get`](types/collections.md#dictionary-operations) - [`items`](types/collections.md#loop-through-a-dictionary) - [`key`](types/collections.md#dictionaries) - [`len`](types/collections.md#inspect_1) - [`loop`](types/collections.md#loop-through-a-dictionary) - [`pop`](types/collections.md#remove_1) - [`popitem`](types/collections.md#remove_1) - [`update`](types/collections.md#update_1) - [`value`](types/collections.md#dictionaries) - [`values`](types/collections.md#loop-through-a-dictionary) - - [**`tuples`**](types/collections.md#tuples): - [`access items`](types/collections.md#access-items) - [`boolean expressions`](types/collections.md#boolean-expressions_2) - [`count`](types/collections.md#inspect_2) - [`immmutable`](types/collections.md#tuples) - [`index`](types/collections.md#tuples) - [`index`](types/collections.md#inspect_2) - [`len`](types/collections.md#inspect_2) - [`loop`](types/collections.md#loop-through-a-tuple) - [`max`](types/collections.md#arithmetic_1) - [`min`](types/collections.md#arithmetic_1) - [`packing`](types/collections.md#packing-and-unpacking) - [`sum`](types/collections.md#arithmetic_1) - [`tuple`](types/collections.md#create_2) - [`unpacking`](types/collections.md#packing-and-unpacking) - {: data-fcm-hide="essentials" } - - [**`sets`**](types/collections.md#sets): - [`add`](types/collections.md#update_1) - [`boolean expressions`](types/collections.md#boolean-expressions_3) - [`clear`](types/collections.md#remove_1) - [`copy`](types/collections.md#create_3) - [`discard`](types/collections.md#remove_1) - [`isdisjoint`](types/collections.md#compare) - [`issubset`](types/collections.md#compare) - [`issuperset`](types/collections.md#compare) - [`len`](types/collections.md#inspect_3) - [`loop`](types/collections.md#loop-through-a-set) - [`max`](types/collections.md#arithmetic_2) - [`min`](types/collections.md#arithmetic_2) - [`pop`](types/collections.md#remove_1) - [`remove`](types/collections.md#remove_1) - [`set`](types/collections.md#create_3) - [`sum`](types/collections.md#arithmetic_2) - [`update`](types/collections.md#update_1) - [`| & - ^`](types/collections.md#combine) - {: data-fcm-hide="essentials" } - -
    -
    - -
    -#### Control flow { .pt-homepage-heading } - -
    - -- :material-source-branch:{ .lg .middle } [__Conditionals__](flow/conditionals.md) - - Decision points that run code only if a condition is met. - - [**`if, elif, else`**](flow/conditionals.md#if-elif-else): - [`and, or, not`](flow/conditionals.md#logical-operators) - [`boolean expressions`](flow/conditionals.md#boolean-expressions) - - [**`match, case`**](flow/conditionals.md#match-case): - [`_ wildcard`](flow/conditionals.md#default-value-_) - [`case + if`](flow/conditionals.md#case-if) - [`match with |`](flow/conditionals.md#match-multiple-values-with) - [`unpacking`](flow/conditionals.md#unpacking-a-tuple) - - [**`control flow`**](flow/conditionals.md#control-flow-statements): - [`break`](flow/conditionals.md#break) - [`continue`](flow/conditionals.md#continue) - [`pass`](flow/conditionals.md#going-further_2) - -- :material-repeat:{ .lg .middle } [__Loops__](flow/loops.md) - - Repeat a block of code multiple times. - - [**`for`**](flow/loops.md#for-loops): - [`enumerate`](flow/loops.md#loop-with-index-and-value) - [`loop a set number of times`](flow/loops.md#loop-a-certain-number-of-times) - [`loop through a collection`](flow/loops.md#loop-through-a-collection) - [`range`](flow/loops.md#iterable-range) - [`reversed`](flow/loops.md#loop-in-reverse) - [`zip`](flow/loops.md#loop-with-index-and-value) - - [**`while`**](flow/loops.md#while-loops): - [`and`](flow/loops.md#logical-operators) - [`boolean expressions`](flow/loops.md#boolean-expressions) - [`counter and flag names`](flow/loops.md#counter-and-flag-names) - [`flag`](flow/loops.md#using-a-flag) - [`not`](flow/loops.md#logical-operators) - [`or`](flow/loops.md#logical-operators) - [`sentinel`](flow/loops.md#sentinel) - - [**`common patterns`**](flow/loops.md#common-patterns): - [`accumulator`](flow/loops.md#accumulator) - [`counter`](flow/loops.md#counter) - [`nested loops`](flow/loops.md#nested-loops) - - [**`control flow`**](flow/loops.md#control-flow-statements): - [`break`](flow/loops.md#break) - [`continue`](flow/loops.md#continue) - [`else`](flow/loops.md#else) - [`pass`](flow/loops.md#going-further_2) - - -
    -
    - -
    -#### Organization { .pt-homepage-heading } - -
    - -- :material-function-variant:{ .lg .middle } [__Functions__](organization/functions.md) - - Package a named block of code to run it at any time. - - [**`def`**](organization/functions.md#defining-a-function): - [`**kwargs`](organization/functions.md#kwargs-dict) - [`*args`](organization/functions.md#args-tuple) - [`defaults`](organization/functions.md#default-values) - [`docstrings`](organization/functions.md#docstrings) - [`parameters`](organization/functions.md#parameters) - [`pass`](organization/functions.md#pass-placeholder) - [`return`](organization/functions.md#return-values) - - [`combining argument types`](organization/functions.md#combining-categories) - [`keyword-only`](organization/functions.md#keyword-only) - [`positional-only`](organization/functions.md#positional-only) - [`type hints`](organization/functions.md#type-hints) - {: data-fcm-hide="essentials" } - - [**`calling a function`**](organization/functions.md#calling-a-function): - [`arguments`](organization/functions.md#arguments) - [`keyword`](organization/functions.md#by-keyword) - [`required`](organization/functions.md#required) - [`return value`](organization/functions.md#saving-the-return-value) - [`unpacking`](organization/functions.md#unpacking) - - [**`scope`**](organization/functions.md#scope): - [`local vs global`](organization/functions.md#local-vs-global-variables) - - [**`recursion`**](organization/functions.md#recursion) - {: data-fcm-hide="essentials" } - - [**`decorators`**](organization/functions.md#decorators): - [`arguments`](organization/functions.md#accepting-arguments) - [`identity`](organization/functions.md#advanced-uses) - [`original function`](organization/functions.md#returning-the-original-function) - [`stacking`](organization/functions.md#advanced-uses) - [`wrapping`](organization/functions.md#wrapping-the-call) - {: data-fcm-hide="essentials" } - - [**`generators`**](organization/functions.md#generators): - [`generator expressions`](organization/functions.md#generator-expressions) - [`memory`](organization/functions.md#memory-efficiency) - [`yield`](organization/functions.md#yield-vs-return) - {: data-fcm-hide="essentials" } - -- :material-package-variant:{ .lg .middle } [__Classes__](organization/classes.md) - - Bundle related values and functions to a reusable blueprint for similar objects. - - [**`class`**](organization/classes.md#defining-a-class): - [`__init__()`](organization/classes.md#the-__init__-method) - [`class attributes`](organization/classes.md#class-attributes) - [`instance attributes`](organization/classes.md#instance-attributes) - [`methods`](organization/classes.md#object-methods) - [`self`](organization/classes.md#the-self-parameter) - - [**`method decorators`**](organization/classes.md#method-decorators): - [`@classmethod`](organization/classes.md#classmethod) - [`@property`](organization/classes.md#property) - [`@staticmethod`](organization/classes.md#staticmethod) - {: data-fcm-hide="essentials" } - - [**`inheritance`**](organization/classes.md#inheritance): - [`adding attributes and methods`](organization/classes.md#adding-attributes-and-methods) - [`__init__()`](organization/classes.md#overriding-__init__) - [`overriding`](organization/classes.md#overriding-methods) - [`super()`](organization/classes.md#using-super) - - [`multiple inheritance`](organization/classes.md#multiple-inheritance) - {: data-fcm-hide="essentials" } - - [**`polymorphism`**](organization/classes.md#polymorphism): - [`inheritance`](organization/classes.md#polymorphism-via-inheritance) - [`duplicate method names`](organization/classes.md#duplicate-method-names) - {: data-fcm-hide="essentials" } - - [**`encapsulation`**](organization/classes.md#encapsulation): - [`@property`](organization/classes.md#controlled-access-with-property) - [`double underscore`](organization/classes.md#double-underscore) - [`single underscore`](organization/classes.md#single-underscore) - {: data-fcm-hide="essentials" } - - [**`operator overloading`**](organization/classes.md#operator-overloading): - [`__add__`](organization/classes.md#arithmetic-with-__add__) - [`__eq__ and __lt__`](organization/classes.md#comparing-with-__eq__-and-__lt__) - {: data-fcm-hide="essentials" } - - [**`dataclasses`**](organization/classes.md#dataclasses) - {: data-fcm-hide="essentials" } - - [**`abstract base classes`**](organization/classes.md#abstract-base-classes) - {: data-fcm-hide="essentials" } - -
    -
    - -
    -#### External files and resources { .pt-homepage-heading } - -
    - -- :material-import:{ .lg .middle } [__Modules & Imports__](resources/modules.md) - - Splitting code across files, and using someone else's code. - - [**`import`**](resources/modules.md#importing-modules): - [`as`](resources/modules.md#as) - [`from`](resources/modules.md#from) - [`import`](resources/modules.md#import) - [`import order`](resources/modules.md#order-of-multiple-imports) - [`nested paths`](resources/modules.md#nested-paths) - [`packages`](resources/modules.md#packages) - - [**`your own module`**](resources/modules.md#creating-your-own-module): - [`main guard`](resources/modules.md#the-main-guard) - - [**`module, package, library`**](resources/modules.md#modules-vs-packages-vs-libraries) - -- :material-file-document-outline:{ .lg .middle } [__Reading & Writing Files__](resources/files.md) - - Read and write text files on your computer. - - [**`open`**](resources/files.md#opening-and-closing-files): - [`modes`](resources/files.md#modes-options) - [`paths`](resources/files.md#file-paths) - [`with`](resources/files.md#with) - - [**`read()`**](resources/files.md#read): - [`existing`](resources/files.md#r-read-existing) - [`functions`](resources/files.md#functions) - [`modes`](resources/files.md#modes) - [`read()`](resources/files.md#whole-file) - [`readline()`](resources/files.md#by-line) - [`readlines()`](resources/files.md#by-line) - [`seek()`](resources/files.md#seek-and-tell) - [`tell()`](resources/files.md#seek-and-tell) - - [**`write()`**](resources/files.md#write): - [`append`](resources/files.md#a-append) - [`create`](resources/files.md#x-create) - [`functions`](resources/files.md#functions_1) - [`modes`](resources/files.md#modes_1) - [`overwrite`](resources/files.md#w-overwrite) - [`write()`](resources/files.md#single-string) - [`writelines()`](resources/files.md#multiple-strings) - - [**`related libraries`**](resources/files.md#related-libraries) - -
    -
    - -
    -#### Robust programming practices { .pt-homepage-heading } - -
    - -- :material-palette-outline:{ .lg .middle } [__Style__](practices/style.md) - - Readable Python code, and polished UI. - - [**`PEP 8`**](practices/style.md#pep-8-style-guide): - [`blank lines`](practices/style.md#blank-lines) - [`docstrings`](practices/style.md#docstrings) - [`naming`](practices/style.md#naming) - [`whitespace`](practices/style.md#whitespace) - - [`comments`](practices/style.md#comments) - [`constants`](practices/style.md#constants) - [`indentation`](practices/style.md#indentation) - [`order`](practices/style.md#file-order) - [`quote style`](practices/style.md#quote-style) - {: data-fcm-hide="essentials" } - - [**`Linters, formatters`**](practices/style.md#linters-and-formatters) - - [**`Pythonic patterns`**](practices/style.md#pythonic-patterns): - [`mutable defaults`](practices/style.md#mutable-default-arguments) - [`is None`](practices/style.md#is-none-instead-of-none) - - [`truthy checks`](practices/style.md#truthy-checks) - [`enumerate()`](practices/style.md#enumerate-instead-of-range) - {: data-fcm-hide="essentials" } - - [**`Efficiency`**](practices/style.md#efficiency) - [`big O`](practices/style.md#big-o-notation) - [`common optimizations`](practices/style.md#common-optimizations) - [`time`](practices/style.md#time-and-space) - [`space`](practices/style.md#time-and-space) - {: data-fcm-hide="essentials" } - - [**`Polished UX`**](practices/style.md#polished-ux): - [`input validation`](practices/style.md#input-validation) - [`menus`](practices/style.md#menus) - [`randomize`](practices/style.md#randomize-messages) - - [**`Polished UI`**](practices/style.md#polished-ui): - [`background`](practices/style.md#color-styling) - [`bold`](practices/style.md#color-styling) - [`escape sequences`](practices/style.md#escape-sequences) - [`color`](practices/style.md#color-styling) - [`highlighting`](practices/style.md#color-styling) - [`multi-line strings`](practices/style.md#multi-line-strings) - [`formatting variables`](practices/style.md#formatting-variables) - [`underline`](practices/style.md#color-styling) - [`unicode symbols`](practices/style.md#unicode-symbols) - [`dividers`](practices/style.md#dividers) - [`boxes`](practices/style.md#boxes) - [`progress bars`](practices/style.md#progress-bars) - -- :material-bug-outline:{ .lg .middle } [__Errors__](practices/errors.md) - - Resolve bugs, read and utilize exceptions. - - [**`kinds`**](practices/errors.md#kinds-of-errors): - [`bugs`](practices/errors.md) - [`exceptions`](practices/errors.md) - [`logic errors`](practices/errors.md#logic-errors) - [`runtime errors`](practices/errors.md#runtime-errors) - [`syntax errors`](practices/errors.md#syntax-errors) - - [**`fixing`**](practices/errors.md#fixing-errors): - [`debugger tool`](practices/errors.md#debugger-tool) - [`debugging strategies`](practices/errors.md#debugging-strategies) - [`isolate problems`](practices/errors.md#isolate-the-problem) - [`print debugging`](practices/errors.md#print-debugging) - [`rubber duck debugging`](practices/errors.md#read-it-out-loud) - [`syntax error message`](practices/errors.md#reading-a-syntax-error-message) - [`testing`](practices/errors.md#detect-errors-with-testing) - [`TODO / FIXME`](practices/errors.md#flag-as-todofixme) - [`tracebacks`](practices/errors.md#reading-a-traceback) - - [**`handling`**](practices/errors.md#handling-errors): - [`assert`](practices/errors.md#assert-a-condition) - [`else`](practices/errors.md#finally) - [`finally`](practices/errors.md#finally) - [`raise`](practices/errors.md#raise-an-exception) - [`try/except`](practices/errors.md#catch-with-tryexcept) - -
    -
    - -# Add-On Libraries - -
    -#### Utilities { .pt-homepage-heading } - -
    - -- :material-format-list-group:{ .lg .middle } [__collections__](libraries/collections.md) -[:material-language-python:](libraries/collections.md){ .pt-lib-badge .pt-lib-badge--builtin title="Built-in — included with Python" } - {: data-fcm-hide="essentials" } - - Specialized containers with advanced functionality. - -- :material-calendar-clock:{ .lg .middle } [__datetime__](libraries/datetime.md) -[:material-language-python:](libraries/datetime.md){ .pt-lib-badge .pt-lib-badge--builtin title="Built-in — included with Python" } - - Calculating and formatting dates and times. - -- :material-square-root-box:{ .lg .middle } [__math__](libraries/math.md) -[:material-language-python:](libraries/math.md){ .pt-lib-badge .pt-lib-badge--builtin title="Built-in — included with Python" } - - Rounding, roots, constants, and logarithms. - -- :material-dice-multiple:{ .lg .middle } [__random__](libraries/random.md) -[:material-language-python:](libraries/random.md){ .pt-lib-badge .pt-lib-badge--builtin title="Built-in — included with Python" } - - Random numbers, random picks, shuffled order. - -- :material-text-search:{ .lg .middle } [__re__](libraries/re.md) -[:material-language-python:](libraries/re.md){ .pt-lib-badge .pt-lib-badge--builtin title="Built-in — included with Python" } - - Regular expressions: searching, extracting, and replacing text by pattern. - -- :material-clock-outline:{ .lg .middle } [__time__](libraries/time.md) -[:material-language-python:](libraries/time.md){ .pt-lib-badge .pt-lib-badge--builtin title="Built-in — included with Python" } - - Reading the system clock, pausing execution, and measuring elapsed time. - -
    -
    - -
    -#### Data analysis { .pt-homepage-heading } - -
    - -- :material-file-delimited-outline:{ .lg .middle } [__csv__](libraries/csv.md) -[:material-language-python:](libraries/csv.md){ .pt-lib-badge .pt-lib-badge--builtin title="Built-in — included with Python" } - - Reading and writing spreadsheets. - -- :material-chart-line:{ .lg .middle } [__matplotlib__](libraries/matplotlib.md) -[:material-download-outline:](libraries/matplotlib.md){ .pt-lib-badge .pt-lib-badge--third-party title="Third-party — install separately with pip" } - - Charts and plots: line, bar, and scatter, built directly from plain Python data. - -- :material-matrix:{ .lg .middle } [__NumPy__](libraries/numpy.md) -[:material-download-outline:](libraries/numpy.md){ .pt-lib-badge .pt-lib-badge--third-party title="Third-party — install separately with pip" } - {: data-fcm-hide="essentials" } - - Fast numeric arrays, with math applied to a whole array at once instead of item by item. - -- :material-table:{ .lg .middle } [__pandas__](libraries/pandas.md) -[:material-download-outline:](libraries/pandas.md){ .pt-lib-badge .pt-lib-badge--third-party title="Third-party — install separately with pip" } - {: data-fcm-hide="essentials" } - - Tabular data: rows and columns, like a spreadsheet, built on top of NumPy. - -
    -
    - -
    -#### APIs { .pt-homepage-heading } - -
    - -- :material-code-json:{ .lg .middle } [__json__](libraries/json.md) -[:material-language-python:](libraries/json.md){ .pt-lib-badge .pt-lib-badge--builtin title="Built-in — included with Python" } - - Reading and writing JSON data: nested dicts and lists, saved to a file or a string. - -- :material-webhook:{ .lg .middle } [__requests__](libraries/requests.md) -[:material-download-outline:](libraries/requests.md){ .pt-lib-badge .pt-lib-badge--third-party title="Third-party — install separately with pip" } - - Fetching data over the internet, like asking a website or API for information. - -
    -
    - -
    -#### Web scraping { .pt-homepage-heading } - -
    - -- :material-pot-steam-outline:{ .lg .middle } [__BeautifulSoup__](libraries/beautifulsoup.md) -[:material-download-outline:](libraries/beautifulsoup.md){ .pt-lib-badge .pt-lib-badge--third-party title="Third-party — install separately with pip" } - - Parsing HTML: finding tags, reading attributes and text, and turning a page into structured data. - -
    -
    - -
    -#### Image editing { .pt-homepage-heading } - -
    - -- :material-image-outline:{ .lg .middle } [__Pillow__](libraries/pillow.md) -[:material-download-outline:](libraries/pillow.md){ .pt-lib-badge .pt-lib-badge--third-party title="Third-party — install separately with pip" } - - Opening, editing, and saving images, built around one Image object. - -
    -
    - -
    -#### Desktop UIs { .pt-homepage-heading } - -
    - -- :material-application-outline:{ .lg .middle } [__Tkinter__](libraries/tkinter.md) -[:material-language-python:](libraries/tkinter.md){ .pt-lib-badge .pt-lib-badge--builtin title="Built-in — included with Python" } - - Creating desktop applications: text, buttons, dropdowns, forms, output, etc. - -
    -
    - -
    -#### Games { .pt-homepage-heading } - -
    - -- :material-turtle:{ .lg .middle } [__turtle__](libraries/turtle.md) -[:material-language-python:](libraries/turtle.md){ .pt-lib-badge .pt-lib-badge--builtin title="Built-in — included with Python" } - - Build small movement-based games with a pen cursor. - -
    -
    - -
    -#### Testing { .pt-homepage-heading } - -
    - -- :material-test-tube:{ .lg .middle } [__pytest__](libraries/pytest.md) -[:material-download-outline:](libraries/pytest.md){ .pt-lib-badge .pt-lib-badge--third-party title="Third-party — install separately with pip" } - - Writing and running tests: assertions, fixtures, and parametrizing. - -
    -
    - -
    -#### Computer vision { .pt-homepage-heading } - -
    - -- :material-face-recognition:{ .lg .middle } [__OpenCV__](libraries/opencv.md) -[:material-download-outline:](libraries/opencv.md){ .pt-lib-badge .pt-lib-badge--third-party title="Third-party — install separately with pip" } - {: data-fcm-hide="essentials" } - - Real-time image and video analysis, built directly on NumPy arrays: color spaces, edge detection, face detection. - -
    -
    - -
    + diff --git a/docs/javascripts/cheatsheet_button.js b/docs/javascripts/cheatsheet_button.js deleted file mode 100644 index 29e40c1..0000000 --- a/docs/javascripts/cheatsheet_button.js +++ /dev/null @@ -1,41 +0,0 @@ -(function () { - // Explicit "Cheatsheet" text shortcut to the homepage (already this - // site's compact quick-reference dashboard — see README), replacing the - // logo image in the header's top-left slot rather than sitting next to - // it — the logo already linked home with no other purpose, so the pill - // takes over that exact role instead of duplicating it. Styled in - // extra.css to match the Essentials/Advanced toggle's own active/ - // inactive look. The logo itself is hidden via CSS (.md-header__button - // .md-logo { display: none }), not removed here, so this script only - // owns inserting the pill. - function render() { - const existing = document.querySelector(".pt-cheatsheet-link"); - if (existing) existing.remove(); - - const title = document.querySelector(".md-header__title"); - const logo = document.querySelector(".md-header__button.md-logo"); - if (!title || !logo) return; - - const link = document.createElement("a"); - link.className = "pt-cheatsheet-link"; - link.href = logo.getAttribute("href"); - link.textContent = "Cheatsheet"; - - // Same homepage check as homepage_header_title.js — kept independent - // rather than shared, since one more `===` comparison isn't worth a - // cross-file dependency between two otherwise-unrelated scripts. - const isHomepage = window.location.pathname.replace(/index\.html$/, "") === "/"; - if (isHomepage) { - link.classList.add("pt-cheatsheet-link--active"); - link.setAttribute("aria-current", "page"); - } - - title.insertAdjacentElement("beforebegin", link); - } - - if (window.document$) { - window.document$.subscribe(render); - } else { - document.addEventListener("DOMContentLoaded", render); - } -})(); diff --git a/docs/javascripts/theme_toggle.js b/docs/javascripts/theme_toggle.js index 50bf86b..b7055f2 100644 --- a/docs/javascripts/theme_toggle.js +++ b/docs/javascripts/theme_toggle.js @@ -9,20 +9,20 @@ // it's now the mkdocs-audience-toggle plugin (see mkdocs.yml and // CLAUDE.md's "Planned extraction" section) and no longer touches this // code. This file keeps only the one bit that plugin can't own: - // recomputing --pt-lib-span (the add-on-library boxes' grid span) once + // recomputing --library-span (the add-on-library boxes' grid span) once // the plugin hides some of their cards — see updateLibrarySpans below. - // pt-lib--N sizes each library box for its full card count; hiding cards + // The cheatsheet plugin's --cards-N class sizes each library box for its full card count; hiding cards // in Essentials mode leaves boxes too wide. Recompute the visible count - // into --pt-lib-span so extra.css can override pt-lib--N while active. + // into --library-span so extra.css can override --cards-N while active. function updateLibrarySpans() { - document.querySelectorAll(".pt-category--wide").forEach(function (box) { + document.querySelectorAll(".library-grid > .md-cheatsheet__group").forEach(function (box) { const cards = box.querySelectorAll(".grid.cards > ul > li"); let visible = 0; cards.forEach(function (li) { if (getComputedStyle(li).display !== "none") visible++; }); - if (visible > 0) box.style.setProperty("--pt-lib-span", Math.min(visible, 4)); + if (visible > 0) box.style.setProperty("--library-span", Math.min(visible, 4)); }); } @@ -32,8 +32,8 @@ // correctly ordered regardless of which script's DOMContentLoaded/ // document$ subscriber happens to run first. function setUpLibrarySpanRecompute() { - if (window.__ptLibSpanObserverBound) return; - window.__ptLibSpanObserverBound = true; + if (window.__librarySpanObserverBound) return; + window.__librarySpanObserverBound = true; new MutationObserver(updateLibrarySpans).observe(document.documentElement, { attributeFilter: ["data-fcm-mode"], }); diff --git a/docs/libraries/json.md b/docs/libraries/apis/json.md similarity index 87% rename from docs/libraries/json.md rename to docs/libraries/apis/json.md index f03ec48..3ade3b7 100644 --- a/docs/libraries/json.md +++ b/docs/libraries/apis/json.md @@ -1,4 +1,7 @@ --- +cheatsheet_title: json +cheatsheet_description: 'Reading and writing JSON data: nested dicts and lists, saved to a file or a string.' +cheatsheet_title_suffix: :material-language-python:{ .library-badge .library-badge--builtin title="Built-in — included with Python" } description: >- Reading and writing JSON data in Python with the json module: json.load, json.dump, and working with nested data, with runnable examples. @@ -16,7 +19,7 @@ The **`json`** module reads and writes JSON ("JavaScript Object Notation") data
    -## Setup { data-card-link="skip" } +## Setup `json` ships with Python's standard library — nothing to install. The whole module is used through the `json.` prefix, so a plain import is all you need. @@ -35,7 +38,7 @@ import json
    -## Writing JSON files +## Writing JSON files { cs="dump" } `json.dump()` writes a Python object straight to an open file — a `dict` becomes a JSON object, a `list` becomes a JSON array, automatically. @@ -79,7 +82,7 @@ print("wrote snake.json")
    -## Reading JSON files +## Reading JSON files { cs="load" } `json.load()` reads a file and reconstructs the original Python object — a JSON object comes back as a `dict`, a JSON array comes back as a `list`, with numbers and booleans already converted to `int`/`float`/`bool` instead of strings. @@ -93,9 +96,9 @@ print(data["species"]) print(data["length_ft"]) ``` -### Nested data +### Nested data { cs="nested data" } -Unlike a [CSV file](csv.md), which is strictly flat rows and columns, JSON can nest a list or another object inside a value — so one record can hold something like a snake's full sighting history, not just single values per column. +Unlike a [CSV file](../data_analysis/csv.md), which is strictly flat rows and columns, JSON can nest a list or another object inside a value — so one record can hold something like a snake's full sighting history, not just single values per column. ```python-ref snake = { @@ -149,7 +152,7 @@ print(data["sightings"][0]) # "2024-03-15"
    -## Working with strings instead of files +## Working with strings instead of files { cs="loads" } `json.dumps()`/`json.loads()` do the same conversion as `dump()`/`load()`, but to and from a string in memory rather than a file — the pair to reach for when the JSON is coming from somewhere other than disk, like an API response. The [`requests`](requests.md) library's own `.json()` method — covered on that page — is really just calling `json.loads()` on the response text for you. diff --git a/docs/libraries/requests.md b/docs/libraries/apis/requests.md similarity index 89% rename from docs/libraries/requests.md rename to docs/libraries/apis/requests.md index 0e33584..ba8ece7 100644 --- a/docs/libraries/requests.md +++ b/docs/libraries/apis/requests.md @@ -1,4 +1,7 @@ --- +cheatsheet_title: requests +cheatsheet_description: Fetching data over the internet, like asking a website or API for information. +cheatsheet_title_suffix: :material-download-outline:{ .library-badge .library-badge--third-party title="Third-party — install separately with pip" } description: >- Making HTTP requests in Python with the requests library: fetching data, checking status codes, parsing JSON, and handling errors. @@ -18,7 +21,7 @@ requests is an open-source project maintained by volunteer contributors.
    -## Setup { data-card-link="skip" } +## Setup ```bash pip install requests @@ -54,17 +57,17 @@ For everyday use, `requests` offers the best balance of simplicity and capabilit
    -## How a request works +## How a request works { cs="how requests work" } Making a request from Python works the same way a browser does, minus the part where anything gets drawn on screen. Your program opens a connection to a server at a URL, sends a **request** — the URL itself, plus optional headers and query parameters — and waits. The server does whatever work that URL asks for, then sends back a **response**: a status code summarizing what happened, a few headers of its own, and usually a body of data. Nothing renders automatically the way a browser would — `.text`/`.json()` just hand that raw body to your code, as a plain string or a Python `dict`/`list`. -Most APIs send that body back as [JSON](json.md) — data meant to be read by a program. A URL meant for people instead sends back HTML, the same raw markup [BeautifulSoup](beautifulsoup.md#html-and-web-pages) parses when scraping a page instead of calling an API. +Most APIs send that body back as [JSON](json.md) — data meant to be read by a program. A URL meant for people instead sends back HTML, the same raw markup [BeautifulSoup](../web/beautifulsoup.md#html-and-web-pages) parses when scraping a page instead of calling an API.
    -## Making a request +## Making a request { cs="get" } `requests.get(url)` sends a request and returns a `Response` object holding whatever came back. @@ -76,7 +79,7 @@ print(response.status_code) # 200 print(response.text) # '{"userId": 1, "id": 1, "title": "...", "body": "..."}' ``` -### Checking the status code +### Checking the status code { cs="status_code" } `.status_code` tells you whether the request actually succeeded before you try to use the data. The most common codes: `200` (success), `201` (a `POST` created something new), `404` (that endpoint/resource doesn't exist), `401`/`403` (missing or invalid permission), `500` (the server itself failed). `.raise_for_status()` is a shortcut that raises an exception automatically for any failing code, instead of checking `.status_code` by hand every time. @@ -96,9 +99,9 @@ response.raise_for_status() # does nothing on 200, raises on a failing code print("request succeeded") ``` -### Parsing JSON +### Parsing JSON { cs="json" } -`.json()` converts a JSON response body directly into a Python `dict` or `list`. Most web APIs send their data back as JSON — text formatted so it maps directly onto Python's own `dict`/`list` structures, which is why `.json()` needs no extra parsing step. Once converted, the result works exactly like any other [dict](../types/collections.md#dictionaries) or [list](../types/collections.md#lists) you'd build by hand. +`.json()` converts a JSON response body directly into a Python `dict` or `list`. Most web APIs send their data back as JSON — text formatted so it maps directly onto Python's own `dict`/`list` structures, which is why `.json()` needs no extra parsing step. Once converted, the result works exactly like any other [dict](../../types/collections.md#dictionaries) or [list](../../types/collections.md#lists) you'd build by hand. ```python-ref response = requests.get("https://jsonplaceholder.typicode.com/posts/1") @@ -117,7 +120,7 @@ print(post["title"]) print(post["body"]) ``` -### Query parameters +### Query parameters { cs="params" } Pass a `params` dict instead of hand-building the URL's `?key=value` text yourself. `requests` builds the query string for you — including escaping special characters correctly — so `params={"postId": 1}` is both safer and easier to read than string-formatting the URL by hand. @@ -140,7 +143,7 @@ comments = response.json() print(len(comments)) ``` -### Custom headers +### Custom headers { cs="headers" } Pass a `headers` dict to attach extra metadata to a request — an API key, a content type, or a `User-Agent` identifying what's making the request. `requests` sends a generic default `User-Agent` if none is given. @@ -162,13 +165,13 @@ print(response.status_code) ``` ??? warning "Some sites block the default User-Agent" - Plenty of real websites (as opposed to test APIs like this page's own examples) return a `403 Forbidden` for any request that doesn't look like it came from an actual browser, since `requests`' own default `User-Agent` string identifies it as a script. Setting `headers={"User-Agent": "Mozilla/5.0"}` (or a similar browser-like string) is often enough to get past this — worth remembering the moment a real page's request stops working right after [BeautifulSoup](beautifulsoup.md) worked fine on a test one. + Plenty of real websites (as opposed to test APIs like this page's own examples) return a `403 Forbidden` for any request that doesn't look like it came from an actual browser, since `requests`' own default `User-Agent` string identifies it as a script. Setting `headers={"User-Agent": "Mozilla/5.0"}` (or a similar browser-like string) is often enough to get past this — worth remembering the moment a real page's request stops working right after [BeautifulSoup](../web/beautifulsoup.md) worked fine on a test one.
    -## Sending data +## Sending data { cs="post" } Not every request is asking for something back — `requests.post()` sends data *to* a URL instead, the same way submitting a form or creating a new resource through an API works. Pass a Python dict as `json=`, and `requests` handles converting it to a JSON string and setting the right header for you. @@ -196,9 +199,9 @@ print(response.json())
    -## Handling request errors +## Handling request errors { cs="error handling" } -A network call can fail in ways that have nothing to do with your code — the [Errors](../practices/errors.md#catch-with-tryexcept) page covers `try`/`except` in general; a couple of exceptions are specific to `requests`. +A network call can fail in ways that have nothing to do with your code — the [Errors](../../practices/errors.md#catch-with-tryexcept) page covers `try`/`except` in general; a couple of exceptions are specific to `requests`. | Exception | Happens when | |-----------|---------------| diff --git a/docs/libraries/opencv.md b/docs/libraries/computer_vision/opencv.md similarity index 88% rename from docs/libraries/opencv.md rename to docs/libraries/computer_vision/opencv.md index 01573db..53c27b4 100644 --- a/docs/libraries/opencv.md +++ b/docs/libraries/computer_vision/opencv.md @@ -1,4 +1,9 @@ --- +cheatsheet_title: OpenCV +cheatsheet_description: 'Real-time image and video analysis, built directly on NumPy arrays: color spaces, edge detection, face detection.' +cheatsheet_title_suffix: :material-download-outline:{ .library-badge .library-badge--third-party title="Third-party — install separately with pip" } +cheatsheet_attrs: + data-fcm-hide: essentials description: >- Computer vision in Python with OpenCV: reading and displaying images, color spaces, edge detection, contours, and face detection. @@ -12,13 +17,13 @@ description: >- OpenCV is stewarded by nonprofit [OpenCV.org](https://opencv.org/). -**OpenCV** (imported as `cv2`) is a popular library for computer vision — real-time image and video analysis, rather than the straightforward photo editing [Pillow](pillow.md) is built for. It's a third-party package, originally written in C++ with a thin Python wrapper over it, which shows up in a couple of its API choices: images load as plain NumPy arrays instead of a dedicated `Image` class, and in **BGR** (blue-green-red) channel order rather than the RGB most other tools expect. Like Pillow and [Tkinter](tkinter.md), OpenCV produces visual, often interactive output — a window showing an image or a live camera feed — that can't run inside this site's browser sandbox, so the examples below aren't runnable here. Copy them into a local `.py` file alongside an image and run them with `python` to see the results. +**OpenCV** (imported as `cv2`) is a popular library for computer vision — real-time image and video analysis, rather than the straightforward photo editing [Pillow](../images/pillow.md) is built for. It's a third-party package, originally written in C++ with a thin Python wrapper over it, which shows up in a couple of its API choices: images load as plain NumPy arrays instead of a dedicated `Image` class, and in **BGR** (blue-green-red) channel order rather than the RGB most other tools expect. Like Pillow and [Tkinter](../desktop_uis/tkinter.md), OpenCV produces visual, often interactive output — a window showing an image or a live camera feed — that can't run inside this site's browser sandbox, so the examples below aren't runnable here. Copy them into a local `.py` file alongside an image and run them with `python` to see the results.
    -## Setup { data-card-link="skip" } +## Setup ```bash pip install opencv-python @@ -41,7 +46,7 @@ import cv2 | Cascade classifier | A pre-trained model, shipped with OpenCV, that scans an image for a specific object — most commonly a face. | | Frame | One still image out of a video, read and processed one at a time in a loop. | -For a broader comparison of OpenCV against Pillow and other Python image libraries, see the table on the [Pillow page](pillow.md#why-pillow). +For a broader comparison of OpenCV against Pillow and other Python image libraries, see the table on the [Pillow page](../images/pillow.md#why-pillow). | Section | Used for | |---------|----------| @@ -59,7 +64,7 @@ For a broader comparison of OpenCV against Pillow and other Python image librari
    -## Reading, displaying, and saving images +## Reading, displaying, and saving images { cs="reading\, displaying\, saving images" } Every OpenCV workflow starts the same way: load a file into an array, do something to it, then optionally write the result back out. @@ -76,7 +81,7 @@ cv2.destroyAllWindows() cv2.imwrite("snake_copy.png", img) ``` -### Reading a file +### Reading a file { cs="imread" } `cv2.imread(path)` loads immediately into a full NumPy array — unlike Pillow's `Image.open()`, there's no lazy header-only read; the whole pixel grid is decoded right away. `.shape` reports `(height, width, channels)`, the opposite order of Pillow's `.size`, which gives `(width, height)`. If the path is wrong, `imread()` doesn't raise an error — it silently returns `None`, so check for that before doing anything else with the result. @@ -98,7 +103,7 @@ else: print(img.shape) ``` -### Displaying a window +### Displaying a window { cs="displaying a window" } `cv2.imshow(title, img)` opens a window showing the image, but it closes immediately unless paired with `cv2.waitKey(0)`, which pauses the program until a key is pressed. `cv2.destroyAllWindows()` then closes every OpenCV window still open. All three need a real display attached — they're for local development, not headless scripts. @@ -117,7 +122,7 @@ cv2.waitKey(0) cv2.destroyAllWindows() ``` -### Saving a file +### Saving a file { cs="saving a file" } `cv2.imwrite(path, img)` writes the array back to disk, inferring the format from the file extension the same way Pillow's `.save()` does. It returns `True`/`False` instead of raising an exception on failure, so check the return value if the write matters. @@ -138,7 +143,7 @@ print(saved)
    -## Color spaces +## Color spaces { cs="color spaces" } OpenCV loads color images in **BGR** order rather than RGB — a holdover from its early camera-driver roots — so handing a BGR array to a tool that expects RGB (like `matplotlib`) shows swapped colors unless it's converted first. `cv2.cvtColor()` handles every conversion between color spaces. @@ -148,7 +153,7 @@ rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) ``` -### Converting color spaces +### Converting color spaces { cs="cvtColor" } `cv2.cvtColor(img, code)` — the `code` names which conversion to run, always written `COLOR_2`. `COLOR_BGR2GRAY` collapses color down to a single grayscale channel, `COLOR_BGR2RGB` just reorders channels for tools that expect RGB, and `COLOR_BGR2HSV` switches to hue/saturation/value, which turns "everything that's green" into a range check on one channel instead of three. @@ -182,7 +187,7 @@ cv2.imwrite("snake_gray.png", gray)
    -## Basic operations +## Basic operations { cs="basic operations" } In Python, OpenCV images are represented as NumPy `ndarray`s — `Mat` is the name of OpenCV's corresponding matrix/image type in its C++ API — so some "operations" are plain NumPy indexing rather than an OpenCV-specific method, cropping in particular. @@ -192,7 +197,7 @@ cropped = img[50:250, 0:200] rotated = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE) ``` -### Resize +### Resize { cs="resize" } `cv2.resize(img, (width, height))` stretches the image to an exact new size — the same tradeoff as Pillow's `.resize()`, it doesn't preserve the original aspect ratio unless the new dimensions are computed to match it. @@ -209,7 +214,7 @@ resized = cv2.resize(img, (200, 150)) print(resized.shape) ``` -### Cropping +### Cropping { cs="cropping" } Since an OpenCV image in Python is a NumPy array, cropping is a plain slice: `img[y1:y2, x1:x2]` — rows (height) first, then columns (width), the reverse of the `(x, y)` order most drawing functions use. There's no dedicated `.crop()` method to reach for. @@ -226,7 +231,7 @@ cropped = img[50:250, 0:200] print(cropped.shape) ``` -### Rotating +### Rotating { cs="rotating" } `cv2.rotate(img, code)` handles clean 90°-multiple rotations with a fixed set of codes (`ROTATE_90_CLOCKWISE`, `ROTATE_180`, `ROTATE_90_COUNTERCLOCKWISE`). For an arbitrary angle, build a rotation matrix with `cv2.getRotationMatrix2D()` and apply it with `cv2.warpAffine()`. @@ -254,7 +259,7 @@ cv2.imwrite("rotated.jpg", rotated_45)
    -## Drawing shapes and text +## Drawing shapes and text { cs="drawing" } Drawing functions modify a `Mat` directly, in place — there's no separate drawing-context object like Pillow's `ImageDraw.Draw()`. @@ -263,7 +268,7 @@ cv2.rectangle(img, (10, 10), (100, 60), (0, 128, 0), 3) cv2.putText(img, "handler", (15, 90), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 128, 0), 2) ``` -### Shapes and lines +### Shapes and lines { cs="shapes and lines" } `cv2.rectangle()`, `cv2.circle()`, and `cv2.line()` each take two corner/center points, a **BGR** color tuple, and a thickness in pixels — pass `-1` as the thickness to fill the shape solid instead of outlining it. @@ -283,7 +288,7 @@ cv2.circle(canvas, (150, 40), 25, (0, 200, 255), -1) cv2.imwrite("shapes.png", canvas) ``` -### Text +### Text { cs="text" } `cv2.putText()` needs a font (one of the built-in `cv2.FONT_HERSHEY_*` constants — there's no custom font loading the way Pillow's `ImageFont` offers), a size scale rather than a point size, and a position given as the text's **bottom-left** corner rather than its top-left. @@ -304,7 +309,7 @@ cv2.imwrite("labeled.png", canvas)
    -## Thresholding and edge detection +## Thresholding and edge detection { cs="thresholding\, edge detection" } Both operations reduce an image down to just the information that matters for a specific analysis task, throwing away "how bright" or "how gradual" in favor of a hard yes/no per pixel. @@ -314,7 +319,7 @@ _, thresholded = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) edges = cv2.Canny(gray, 100, 200) ``` -### Threshold +### Threshold { cs="threshold" } `cv2.threshold(img, cutoff, max_value, method)` turns a grayscale image into pure black-and-white: every pixel above `cutoff` becomes `max_value` (usually `255`, white), everything else becomes `0` (black). It returns a tuple — the cutoff value actually used, and the resulting image — which is why the example throws the first value away with `_`. @@ -332,7 +337,7 @@ _, thresholded = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) cv2.imwrite("thresholded.png", thresholded) ``` -### Edge detection +### Edge detection { cs="Canny" } `cv2.Canny(img, low, high)` traces outlines wherever brightness changes sharply, and works best on a grayscale image. `low` and `high` set two brightness-change thresholds — a change above `high` is always kept as an edge, a change below `low` is always discarded, and anything in between is kept only if it connects to a pixel already counted as an edge. @@ -353,7 +358,7 @@ cv2.imwrite("edges.png", edges)
    -## Blurring +## Blurring { cs="blurring" } Smoothing an image slightly, before edge detection or thresholding, often removes small specks of noise that would otherwise show up as false edges or scattered dark pixels. @@ -361,7 +366,7 @@ Smoothing an image slightly, before edge detection or thresholding, often remove blurred = cv2.GaussianBlur(img, (5, 5), 0) ``` -### Gaussian blur +### Gaussian blur { cs="gaussian blur" } `cv2.GaussianBlur(img, kernel_size, sigma)` averages each pixel with its neighbors, weighted so nearby pixels count more than far ones. Both numbers in `kernel_size` (width, height) must be odd, and a larger kernel blurs more heavily. `sigma` — the spread of that weighting — can usually be left at `0` to let OpenCV calculate it automatically from the kernel size. @@ -383,7 +388,7 @@ cv2.imwrite("blurred.jpg", blurred)
    -## Contours +## Contours { cs="contours" } A **contour** is a curve joining the continuous points along a shape's boundary — useful for counting objects in an image, measuring their size, or outlining just the shapes rather than the whole image. @@ -392,7 +397,7 @@ contours, _ = cv2.findContours(thresholded, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_ cv2.drawContours(img, contours, -1, (0, 128, 0), 2) ``` -### Finding and drawing contours +### Finding and drawing contours { cs="finding and drawing contours" } `cv2.findContours()` needs a black-and-white image (usually the output of `cv2.threshold()` or `cv2.Canny()`) and returns a list of contours, each a list of boundary points. `cv2.drawContours(img, contours, index, color, thickness)` draws them back onto an image — pass `-1` as the index to draw every contour found rather than just one. @@ -417,7 +422,7 @@ cv2.imwrite("contours.png", img)
    -## Face detection with cascade classifiers +## Face detection with cascade classifiers { cs="CascadeClassifier" } A **cascade classifier** is a pre-trained model, shipped with OpenCV itself, that scans an image at many positions and scales looking for a specific object — most commonly, faces. @@ -426,7 +431,7 @@ face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_fronta faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5) ``` -### Detecting and labeling faces +### Detecting and labeling faces { cs="detecting and labeling faces" } `cv2.data.haarcascades` points to OpenCV's own folder of pre-trained `.xml` cascade files, so no separate download is needed for common detectors like frontal faces. `.detectMultiScale()` returns a list of `(x, y, width, height)` boxes, one per match — looping over them lets you draw a box (and a label) around each one found. @@ -457,7 +462,7 @@ cv2.imwrite("detected.jpg", img)
    -## Working with video +## Working with video { cs="VideoCapture" } A video is really just a sequence of frames, read and processed one at a time — everything covered above (color conversion, drawing, detection) applies to a single video frame exactly the same way it applies to a still image. @@ -474,7 +479,7 @@ capture.release() cv2.destroyAllWindows() ``` -### Reading frames +### Reading frames { cs="reading frames" } `cv2.VideoCapture(source)` opens a webcam (an integer index, `0` for the default camera) or a video file (a path string). `.read()` returns `(success, frame)` each time it's called — `success` becomes `False` once a video file runs out of frames, which is what ends the loop naturally. `cv2.waitKey(1)` keeps the display window responsive and doubles as a keypress check (here, `q` to quit) without blocking the way `waitKey(0)` does. `.release()` frees the camera/file so other programs can use it again. diff --git a/docs/libraries/csv.md b/docs/libraries/data_analysis/csv.md similarity index 92% rename from docs/libraries/csv.md rename to docs/libraries/data_analysis/csv.md index fc98ecc..538a369 100644 --- a/docs/libraries/csv.md +++ b/docs/libraries/data_analysis/csv.md @@ -1,4 +1,7 @@ --- +cheatsheet_title: csv +cheatsheet_description: Reading and writing spreadsheets. +cheatsheet_title_suffix: :material-language-python:{ .library-badge .library-badge--builtin title="Built-in — included with Python" } description: >- Reading and writing CSV files in Python with the csv module: csv.writer, csv.reader, and DictReader, with runnable examples. @@ -16,7 +19,7 @@ The **`csv`** module reads and writes CSV ("comma-separated values") files — a
    -## Setup { data-card-link="skip" } +## Setup `csv` ships with Python's standard library — nothing to install. The whole module is used through the `csv.` prefix, so a plain import is all you need. @@ -35,7 +38,7 @@ import csv
    -## Writing CSV files +## Writing CSV files { cs="writer" } `csv.writer` wraps an open file and turns each list you pass to `.writerow()` into one comma-separated line. @@ -87,7 +90,7 @@ print("wrote snakes.csv")
    -## Reading CSV files +## Reading CSV files { cs="reader" } `csv.reader` gives back each row as a plain list of strings — including the header row, which is usually skipped over explicitly. @@ -107,7 +110,7 @@ with open("snakes.csv", newline="") as file: print(row) ``` -### Reading rows as dictionaries +### Reading rows as dictionaries { cs="DictReader" } Uses the first row as column names automatically, so each row comes back as a `dict`. You can look up values by column name instead of by position. Every value is still read as a plain string — convert it (e.g. with `float()`) if you need to do math on it. diff --git a/docs/libraries/matplotlib.md b/docs/libraries/data_analysis/matplotlib.md similarity index 87% rename from docs/libraries/matplotlib.md rename to docs/libraries/data_analysis/matplotlib.md index bf1ac57..7d41f98 100644 --- a/docs/libraries/matplotlib.md +++ b/docs/libraries/data_analysis/matplotlib.md @@ -1,4 +1,7 @@ --- +cheatsheet_title: matplotlib +cheatsheet_description: 'Charts and plots: line, bar, and scatter, built directly from plain Python data.' +cheatsheet_title_suffix: :material-download-outline:{ .library-badge .library-badge--third-party title="Third-party — install separately with pip" } description: >- Creating charts in Python with matplotlib: line plots, bar charts, scatter plots, subplots, and saving figures to a file. @@ -12,13 +15,13 @@ description: >- matplotlib is an open-source project, funded by nonprofit [NumFOCUS](https://numfocus.org/). -**matplotlib** (its plotting interface imported as `plt`) is Python's foundational library for creating charts — line plots, bar charts, scatter plots — directly from plain Python data. It's a third-party package, not part of the standard library, but it's a foundational Python plotting library that many other Python tools integrate with or build upon. Like [Pillow](pillow.md) and [OpenCV](opencv.md), matplotlib produces visual output — a chart shown in a window or saved to a file — which can't be shown inside this site's browser sandbox, so the examples below aren't runnable here. Copy them into a local `.py` file and run them with `python` to see the results. +**matplotlib** (its plotting interface imported as `plt`) is Python's foundational library for creating charts — line plots, bar charts, scatter plots — directly from plain Python data. It's a third-party package, not part of the standard library, but it's a foundational Python plotting library that many other Python tools integrate with or build upon. Like [Pillow](../images/pillow.md) and [OpenCV](../computer_vision/opencv.md), matplotlib produces visual output — a chart shown in a window or saved to a file — which can't be shown inside this site's browser sandbox, so the examples below aren't runnable here. Copy them into a local `.py` file and run them with `python` to see the results.
    -## Setup { data-card-link="skip" } +## Setup ```bash pip install matplotlib @@ -52,7 +55,7 @@ For everyday charts, matplotlib offers the most control and the widest compatibi
    -## Line plots +## Line plots { cs="line plots" } `plt.plot(x, y)` draws a line connecting a series of x/y points — matplotlib's most basic and most common chart, given two equal-length sequences of numbers. @@ -66,7 +69,7 @@ plt.plot(years, length_ft) plt.show() ``` -### Labels and title +### Labels and title { cs="labels and title" } `plt.xlabel()`, `plt.ylabel()`, and `plt.title()` label a chart's axes and give it a heading — without them, a chart is just numbers with no explanation of what they mean. @@ -78,7 +81,7 @@ plt.title("ball python growth") plt.show() ``` -### Multiple lines and a legend +### Multiple lines and a legend { cs="multiple lines and a legend" } Calling `plt.plot()` more than once before `plt.show()` draws every line onto the same figure. Passing `label=` to each call, then `plt.legend()`, adds a key showing which line is which. @@ -107,7 +110,7 @@ plt.show()
    -## Bar charts +## Bar charts { cs="bar charts" } `plt.bar(labels, values)` draws one bar per label — suited to comparing a value across categories, rather than showing change over a continuous range the way a line plot does. @@ -126,7 +129,7 @@ plt.show()
    -## Scatter plots +## Scatter plots { cs="scatter plots" } `plt.scatter(x, y)` plots individual points instead of connecting them with a line — suited to showing the relationship between two measurements without implying an order between them. @@ -146,7 +149,7 @@ plt.show()
    -## Subplots +## Subplots { cs="subplots" } `plt.subplots(rows, cols)` returns a `Figure` and a grid of `Axes` objects, for placing more than one chart side by side instead of calling `plt.show()` separately for each. Each `Axes` in the grid gets its own `.plot()`/`.bar()`/`.scatter()` and its own `.set_title()`, rather than the `plt.`-prefixed functions used above. @@ -168,7 +171,7 @@ plt.show()
    -## Saving a figure +## Saving a figure { cs="saving a figure" } `plt.savefig(filename)` writes the current figure to a file instead of opening a window — the way to produce a chart image for a report, a webpage, or anywhere a live Python process won't be running to show it. diff --git a/docs/libraries/numpy.md b/docs/libraries/data_analysis/numpy.md similarity index 87% rename from docs/libraries/numpy.md rename to docs/libraries/data_analysis/numpy.md index d0fcd64..ddc795f 100644 --- a/docs/libraries/numpy.md +++ b/docs/libraries/data_analysis/numpy.md @@ -1,4 +1,9 @@ --- +cheatsheet_title: NumPy +cheatsheet_description: Fast numeric arrays, with math applied to a whole array at once instead of item by item. +cheatsheet_title_suffix: :material-download-outline:{ .library-badge .library-badge--third-party title="Third-party — install separately with pip" } +cheatsheet_attrs: + data-fcm-hide: essentials description: >- Fast numeric arrays in Python with NumPy: creating arrays, vectorized math, aggregation, and boolean-mask filtering, with runnable examples. @@ -18,7 +23,7 @@ NumPy is an open-source project, with fiscal sponsorship from the nonprofit [Num
    -## Setup { data-card-link="skip" } +## Setup ```bash pip install numpy @@ -39,7 +44,7 @@ import numpy as np
    -## Creating arrays +## Creating arrays { cs="ndarray" } `np.array()` builds an `ndarray` from an existing list — every value gets converted to the same type. @@ -51,7 +56,7 @@ print(lengths_ft) print(lengths_ft.dtype) ``` -### Building arrays without a list +### Building arrays without a list { cs="arange" } `np.zeros(n)` builds an array of `n` zeros as a starting point to fill in later. `np.arange(stop)` counts up from `0` to (but not including) `stop`, just like the built-in `range()` — with an optional start and step, exactly like `range()` too. @@ -82,7 +87,7 @@ np.arange(0, 10, 2) # array([0, 2, 4, 6, 8])
    -## Array operations +## Array operations { cs="array operations" } A math operation on an array applies to every element at once — no loop required, and considerably faster than looping over a plain list. @@ -101,13 +106,13 @@ print(lengths_m) | Python `for` loop | O(n) | — | | Vectorized | O(n) (same class, smaller constant) | — | - A Python `for` loop over a list and a vectorized NumPy operation both touch every element once — O(n) either way, the same [Big O](../practices/style.md#big-o-notation) class. - + A Python `for` loop over a list and a vectorized NumPy operation both touch every element once — O(n) either way, the same [Big O](../../practices/style.md#big-o-notation) class. + The speed difference is a constant factor, not the order of growth: each pass of a Python loop pays the interpreter's per-iteration overhead, while a vectorized operation runs its loop once, in compiled C, underneath a single Python call. That overhead is small per element but adds up — the larger the array, the bigger the gap, even though neither approach's growth rate has changed. - See [Efficiency](../practices/style.md#efficiency) for why this distinction matters. + See [Efficiency](../../practices/style.md#efficiency) for why this distinction matters. -### Aggregating an array +### Aggregating an array { cs="mean" } 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. @@ -118,7 +123,7 @@ lengths_ft.max() # 12.0 lengths_ft.sum() # 30.5 ``` -### Filtering with a boolean mask +### Filtering with a boolean mask { cs="boolean mask" } Comparing an array to a number produces a same-size array of `True`/`False` values — a **boolean mask**. Indexing the array with that mask keeps only the elements where it's `True`. This is the standard way to filter a NumPy array, instead of writing an explicit loop with an `if` inside it. diff --git a/docs/libraries/pandas.md b/docs/libraries/data_analysis/pandas.md similarity index 89% rename from docs/libraries/pandas.md rename to docs/libraries/data_analysis/pandas.md index 06d64eb..0c29ec5 100644 --- a/docs/libraries/pandas.md +++ b/docs/libraries/data_analysis/pandas.md @@ -1,4 +1,9 @@ --- +cheatsheet_title: pandas +cheatsheet_description: 'Tabular data: rows and columns, like a spreadsheet, built on top of NumPy.' +cheatsheet_title_suffix: :material-download-outline:{ .library-badge .library-badge--third-party title="Third-party — install separately with pip" } +cheatsheet_attrs: + data-fcm-hide: essentials description: >- Tabular data in Python with pandas: building a DataFrame, sorting rows, and summarizing columns, with runnable examples. @@ -18,7 +23,7 @@ pandas is an open-source project, funded by nonprofit [NumFOCUS](https://numfocu
    -## Setup { data-card-link="skip" } +## Setup ```bash pip install pandas @@ -39,7 +44,7 @@ import pandas as pd
    -## Building a DataFrame +## Building a DataFrame { cs="DataFrame" } A `DataFrame` is most often built from a list of dicts — one dict per row, with matching keys becoming the column names. @@ -91,7 +96,7 @@ print(snakes)
    -## Working with a DataFrame +## Working with a DataFrame { cs="working with a DataFrame" } A single column pulled out of a `DataFrame` (with `df["column"]`) is a `Series` — comparing it to a value produces a boolean mask, exactly like a NumPy array, for filtering rows. @@ -108,7 +113,7 @@ big_snakes = snakes[snakes["length_ft"] > 5] print(big_snakes) ``` -### Sorting rows +### Sorting rows { cs="sort_values" } Returns the `DataFrame` reordered by a column. `.sort_values("column")` sorts ascending by default, or descending with `ascending=False`. Like most pandas operations, it returns a new `DataFrame` rather than reordering the original in place. @@ -116,7 +121,7 @@ Returns the `DataFrame` reordered by a column. `.sort_values("column")` sorts as snakes.sort_values("length_ft", ascending=False) # rows reordered longest-first ``` -### Summarizing a column +### Summarizing a column { cs="mean" } Calling `.mean()`, `.max()`, or similar directly on a column summarizes it down to a single number. The same way it would on a NumPy array — a `Series` supports the same aggregation methods. diff --git a/docs/libraries/tkinter.md b/docs/libraries/desktop_uis/tkinter.md similarity index 95% rename from docs/libraries/tkinter.md rename to docs/libraries/desktop_uis/tkinter.md index 4e8155d..70d530b 100644 --- a/docs/libraries/tkinter.md +++ b/docs/libraries/desktop_uis/tkinter.md @@ -1,4 +1,7 @@ --- +cheatsheet_title: Tkinter +cheatsheet_description: 'Creating desktop applications: text, buttons, dropdowns, forms, output, etc.' +cheatsheet_title_suffix: :material-language-python:{ .library-badge .library-badge--builtin title="Built-in — included with Python" } description: >- Building desktop GUI applications in Python with Tkinter: widgets, layout managers, event handling, and styling with ttk. @@ -16,7 +19,7 @@ description: >-
    -## Setup { data-card-link="skip" } +## Setup Tkinter ships with the standard library — no extra install is needed on your own machine. `tk` is the near-universal alias for the base module; the themed `ttk` widgets (used throughout this page) are imported separately. @@ -37,7 +40,7 @@ from tkinter import ttk
    -## Creating a window +## Creating a window { cs="Tk" } Every Tkinter app starts the same way: create a root window, add widgets to it, then hand control over to the event loop with `mainloop()` — nothing appears on screen until that final call. @@ -55,7 +58,7 @@ root.mainloop()
    -## Widgets +## Widgets { cs="Button" } A widget is any single element on screen — a button, a text field, a list. Most widgets exist in two versions: a classic one straight from the original `tkinter` module, and a themed one from `tkinter.ttk`, rendered to match the operating system's native look rather than Tkinter's classic (and dated) default style. **Prefer the `ttk` version of a widget whenever one exists** — the examples throughout the rest of this page do. @@ -100,7 +103,7 @@ button.pack() entry.pack() ``` -### Label +### Label { cs } Displays static text (or an image) — no input, no clicks. Mainly used for headings, descriptions, or showing output from other widgets. @@ -119,7 +122,7 @@ label.pack() root.mainloop() ``` -### Button +### Button { cs } Runs a function — passed in as `command` — every time it's clicked. The function itself is defined separately; the button just calls it, with no arguments. @@ -144,7 +147,7 @@ button.pack() root.mainloop() ``` -### Entry +### Entry { cs } A single-line text input box. Call `.get()` on it at any point (usually inside a button's callback) to read whatever the user has typed so far. @@ -196,7 +199,7 @@ root.mainloop()
    -## Layout managers +## Layout managers { cs="pack" } A widget doesn't appear on screen until you tell Tkinter where to put it, using one of three geometry managers. Mixing more than one inside the *same* parent widget causes layout bugs, so pick one per container. @@ -206,7 +209,7 @@ A widget doesn't appear on screen until you tell Tkinter where to put it, using | `grid` | `widget.grid(row=0, column=0)` | Lining widgets up in rows and columns, like a form — the most common choice for anything beyond a trivial layout. | | `place` | `widget.place(x=10, y=10)` | Pinning a widget to an exact pixel position — rarely needed, and doesn't resize gracefully with the window. | -### pack +### pack { cs } Adds a widget to one edge of its parent, and stacks the next widget next to it. `top` by default, or `left`/`right`/`bottom`. It's the simplest manager, but gives you the least control over precise alignment. @@ -225,7 +228,7 @@ ttk.Entry(root).pack(side="left") root.mainloop() ``` -### grid +### grid { cs } Places a widget at a given `row`/`column` inside its parent. The standard choice for form-like layouts, since every widget can be aligned independently of the order it was created in. @@ -269,7 +272,7 @@ root.mainloop()
    -## Configuring widgets +## Configuring widgets { cs="configure" } Every widget has a set of options that control its appearance and behavior — `text`, `width`, `state`, and dozens more depending on the widget type. Set them when you create the widget, or change them afterward with `.configure()` (or the equivalent bracket/dictionary syntax) and read them back with `.cget()`. @@ -279,7 +282,7 @@ label.configure(text="burmese python") # change it later label["text"] # read it back — "burmese python" ``` -### Reading and changing options +### Reading and changing options { cs="reading and changing options" } `.configure(option=value)` changes one or more options after a widget already exists. Handy for updating a `Label` in response to a button click, or disabling an `Entry` while something else is running. `.cget("option")` (or the shorthand `widget["option"]`) reads a single option's current value back out. @@ -310,7 +313,7 @@ root.mainloop()
    -## Handling events +## Handling events { cs="command" } A GUI sits idle until the user does something — Tkinter reacts to that input through callbacks: functions you write once, and hand to Tkinter to call automatically when the right event happens. Every one of those callbacks runs on the same event loop that keeps the window responsive, so a callback that blocks for a while (a long computation, `time.sleep()`, a network request) freezes the entire interface until it returns — use `root.after()` to schedule work in small chunks instead of blocking outright. @@ -321,7 +324,7 @@ def on_click(): ttk.Button(root, text="Go", command=on_click).pack() ``` -### Command callbacks +### Command callbacks { cs="command callbacks" } Most interactive widgets accept a `command` argument that runs whenever the widget is activated. `Button`, `Checkbutton`, `Radiobutton` — pass a function reference (no parentheses, since Tkinter calls it for you). @@ -346,7 +349,7 @@ ttk.Button(root, text="Show Info", command=show_info).pack() root.mainloop() ``` -### Binding events +### Binding events { cs="binding events" } `.bind()` attaches a callback to a named event on any widget. `command` only covers a widget's one "main" action — for anything else (a key press, mouse movement, clicking a label), use `.bind()` instead. The callback receives an `event` object describing what happened. @@ -400,7 +403,7 @@ root.mainloop()
    -## Styling with ttk +## Styling with ttk { cs="Style" } Classic Tkinter widgets render with Tk's original 1990s look, which stands out from every other app on a modern OS — this is exactly the gap `ttk` closes by delegating drawing to the OS's native theme engine. A `ttk.Style` object lets you customize colors and fonts on top of that native look without losing it. @@ -410,7 +413,7 @@ style.configure("TButton", font=("Helvetica", 12)) ttk.Button(root, text="Identify", style="TButton").pack() ``` -### Customizing a style +### Customizing a style { cs="customizing a style" } Every `ttk` widget draws itself according to a named style (`TButton`, `TLabel`, and so on by default). `style.configure()` changes a style's look; `style.theme_use()` switches the whole underlying theme. Defining a new style name (like `"Accent.TButton"` above) lets one specific widget stand out without changing every button in the app. @@ -437,7 +440,7 @@ root.mainloop()
    -## Dialogs +## Dialogs { cs="messagebox" } Tkinter includes a set of ready-made pop-up windows for common tasks — asking a yes/no question, showing an alert, or picking a file — instead of building a `Toplevel` window by hand every time. @@ -448,7 +451,7 @@ messagebox.showinfo("Field Guide", "Species saved.") filedialog.askopenfilename() ``` -### Message boxes +### Message boxes { cs="message boxes" } Covers simple alerts and confirmations. `showinfo`/`showwarning`/`showerror` display a message with an OK button, while `askyesno`/`askokcancel` return `True` or `False` based on the user's choice. @@ -475,7 +478,7 @@ ttk.Button(root, text="Delete", command=delete).pack() root.mainloop() ``` -### File dialogs +### File dialogs { cs="file dialogs" } Opens the OS's native file picker. `askopenfilename()` returns the path the user chose to open; `asksaveasfilename()` returns a path to save to, prompting for a filename if it doesn't already exist. @@ -502,7 +505,7 @@ root.mainloop()
    -## Introspecting widgets +## Introspecting widgets { cs="winfo_width" } Every widget can report details about itself — its size, position, class, or place in the widget hierarchy — through a family of `winfo_*` methods. Useful for debugging a layout, or for writing code that adapts to a widget's actual on-screen size rather than a hardcoded guess. @@ -512,7 +515,7 @@ label.winfo_class() # "TLabel" label.winfo_children() # direct child widgets, if any ``` -### winfo methods +### winfo methods { cs } `winfo_width()`/`winfo_height()` return a widget's current on-screen size in pixels. Note that right after creation this can still be `1`, before the geometry manager has actually placed it (call `root.update()` first if you need an accurate reading immediately). `winfo_class()` returns the underlying Tk widget class name, and `winfo_children()` lists every widget placed directly inside it — handy for looping over a container's contents without keeping a separate list yourself. @@ -542,11 +545,11 @@ root.mainloop()
    -## Putting it together +## Putting it together { cs="putting it together" } A handful of widgets from the table above — `Label`, `Entry`, `Checkbutton`, `Combobox`, `Button` — cover most of what a simple data-entry form needs. This example combines them into one small app: type a species name, toggle whether it's venomous, pick a habitat from a dropdown, then click Submit to display the result. -### A simple form +### A simple form { cs="a simple form" } Each widget stores its value differently, so the `Submit` callback reads each one its own way. `Entry` is read with `.get()` directly, `Checkbutton` is backed by a `BooleanVar` (`is_venomous`) read separately from the widget itself, and `Combobox` is also read with `.get()`. The `Submit` button's callback pulls all three together and updates a `Label` to show the result — the same `command=` pattern covered earlier, just wired to several widgets instead of one. Building the widgets is split into its own `build_form()` function, called once from `main()`, rather than left as loose top-level code. diff --git a/docs/libraries/turtle.md b/docs/libraries/games/turtle.md similarity index 91% rename from docs/libraries/turtle.md rename to docs/libraries/games/turtle.md index 7f8dd46..b3d985e 100644 --- a/docs/libraries/turtle.md +++ b/docs/libraries/games/turtle.md @@ -1,10 +1,13 @@ --- +cheatsheet_title: turtle +cheatsheet_description: Build small movement-based games with a pen cursor. +cheatsheet_title_suffix: :material-language-python:{ .library-badge .library-badge--builtin title="Built-in — included with Python" } description: >- Building small movement-based games in Python with the turtle module: window setup, motion, drawing shapes, the animation loop, and collision detection. --- -# :material-turtle:{ .lg .middle } Turtle library +# :material-turtle:{ .lg .middle } turtle library
    @@ -14,9 +17,9 @@ description: >-
    -## Concepts +## Concepts { cs } -**turtle** draws with a single virtual pen — called a turtle — that sits on a window with a **position** (x,y coordinate) and a **heading** (the direction it's currently facing). `forward()` moves it in that direction, `left()`/`right()` change the heading, and if the pen is down, moving it traces a line behind it. +**turtle** draws with a single virtual pen — called a turtle — that sits on a window with a **position** (x,y coordinate) and a **heading** (the direction it's currently facing). `forward()` moves it in that direction, `left()`/`right()` change the heading, and if the pen is down, moving it traces a line behind it. The window also reacts to keyboard and mouse input, which makes turtle a natural fit for small, no-install games. A game needs a real window and display to run in, so the examples below aren't runnable in the browser — copy them into a local `.py` file to see them in action. @@ -26,7 +29,7 @@ The origins of this library predate ordinary people owning computers: it comes f
    -## Setup { data-card-link="skip" } +## Setup `turtle` ships with the standard library — nothing to install. @@ -34,17 +37,17 @@ The origins of this library predate ordinary people owning computers: it comes f from turtle import * ``` -`*` imports everything at once; if you want to explicitly specify of what you're using, import the precise function names instead (`from turtle import forward, left, done`). +`*` imports everything at once; if you want to explicitly specify of what you're using, import the precise function names instead (`from turtle import forward, left, done`).
    -## The screen +## The screen { cs="Screen" } Everything gets drawn inside one window — the screen. -### Screen setup +### Screen setup { cs="Setup" } Start by creating a window.. @@ -53,7 +56,7 @@ setup(500, 500) # width, height title('My Game') # optional ``` -### Background +### Background { cs } A solid color or a full image, set once on the window itself — not something that needs redrawing every frame. @@ -72,16 +75,16 @@ update() ``` ??? tip "The Tkinter Canvas underneath" - turtle's window is a [Tkinter](tkinter.md) `Canvas` widget underneath — `getcanvas()` returns it directly, for mixing in real Tkinter widgets or features once turtle's own tools stop being enough. + turtle's window is a [Tkinter](../desktop_uis/tkinter.md) `Canvas` widget underneath — `getcanvas()` returns it directly, for mixing in real Tkinter widgets or features once turtle's own tools stop being enough. -### Clear screen +### Clear screen { cs="Clear" } -`clear()` erases drawings, leaving everything else — position, shape, color, event bindings — untouched, which is why it's the one used every frame. +`clear()` erases drawings, leaving everything else — position, shape, color, event bindings — untouched, which is why it's the one used every frame. `clearscreen()` is a full reset instead: drawings gone, every turtle removed, background and bindings back to their defaults, tracer back on. More like starting the whole script over than clearing one frame — useful for a "play again" restart, not for the frame loop itself. -### Colors +### Colors { cs } Anywhere a color is expected — `color()`, `bgcolor()`, `dot()`'s color argument — turtle accepts three formats, all borrowed from Tk rather than defined by Python itself. @@ -94,7 +97,7 @@ Anywhere a color is expected — `color()`, `bgcolor()`, `dot()`'s color argumen There's no small fixed list of named colors — turtle draws from the same [X11 color names](https://en.wikipedia.org/wiki/X11_color_names) Tk uses, a few hundred names in all. `colormode(255)` switches RGB tuples to the more familiar `0`–`255` range instead of `0.0`–`1.0`. -### Closing the window +### Closing the window { cs="Close" } `done()` (covered under The game loop) keeps the window open until it's closed by hand. `exitonclick()` is a common alternative for a finished game: keep the window open, then close it on the next click instead of waiting on the window's own close button. `bye()` closes it immediately, from code, without waiting for a click at all. @@ -107,15 +110,15 @@ exitonclick() # instead of done() — click anywhere to quit
    -## The turtle cursor +## The turtle cursor { cs="turtle cursor" } The turtle is the only thing directly controllable at any moment. Other moveable parts are plain data (Positions and motion) instead of as turtles of their own. (The class-based `Turtle()` interface can create more than one, each independently controllable, but that's a different, more advanced style than the one covered here.) Everything about the turtle itself otherwise falls into four groups: what it looks like, what it draws with (if anything), what shapes it traces, and where it is. -### Shape +### Shape { cs="Appearance" } -#### Show or hide +#### Show or hide { cs="Hide, Show" } The turtle — the small controllable arrow shown by default — is separate from anything it draws. Hiding it doesn't erase existing lines or shapes, and drawing continues normally either way; only the cursor itself disappears. @@ -125,7 +128,7 @@ showturtle() # st() — show it again isvisible() # True or False ``` -#### Shape, color, size +#### Shape, color, size { cs="Color, Shape, Size" } `shape(shape_name)` switches between every built-in shapes. @@ -148,7 +151,7 @@ shapesize(2) # scale it up 2x color('darkgreen', 'green') # outline, fill ``` -#### Custom images +#### Custom images { cs } `register_shape()` installs an image file or a custom polygon as a shape, usable anywhere `shape()` is — a way to swap the cursor for a small custom picture. A limitation is that the custom image won't rotate. The built in shapes above turn to face the turtle's heading as it moves. An image shape always faces the same direction. @@ -156,7 +159,7 @@ color('darkgreen', 'green') # outline, fill register_shape('snake.gif') ``` -#### In a game { data-card-link="skip" } +#### In a game Many games hide the turtle and draws its own shapes instead — the right call once there's a trail, or several independent pieces, that no single turtle could represent alone. A game with just one clearly visible player, though, doesn't need any of that: give the turtle a shape and a color, then move it directly with `goto()`. @@ -182,11 +185,11 @@ def on_hit(x, y): onclick(on_hit) ``` -### Trace movement +### Trace movement { cs="Tracer" } -#### With tracer { data-card-link="skip" } +#### With tracer -The Tracer is whether or not you can see the animation of the turtle moving. +The Tracer is whether or not you can see the animation of the turtle moving. By default, turtle animates its own movement — `forward()`, `goto()`, etc. are drawn bit by bit, animated as if it is moving across the screen. This is called the `tracer` and by default it is True. @@ -204,14 +207,14 @@ 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](../resources/files.md#with), applied to animation instead of a file handle. + A context manager wrapping the same idea as `tracer(False)`/`tracer(True)` — animation is off for whatever runs inside the block, then back on (and shown) once it exits. The same `with` pattern as [opening a file](../../resources/files.md#with), applied to animation instead of a file handle. ```python-ref with no_animation(): circle(50) # drawn instantly, all at once ``` -#### Without tracer { data-card-link="skip" } +#### Without tracer ```python-ref setup(420, 420, 370, 0) @@ -226,13 +229,13 @@ clear() update() ``` -### Ink +### Ink { cs } The "pen" is really the ink behind it: - **Down** means the tip is touching the paper, so ink comes out as the turtle moves. -- **Up** means it's lifted, so moving it leaves no line behind. +- **Up** means it's lifted, so moving it leaves no line behind. | Function | What it does | |---|---| @@ -244,7 +247,7 @@ The "pen" is really the ink behind it: | `fillcolor(fill_color)` | Set fill color. | ```python-ref -color('black', 'yellow') # set outline and fill at once +color('black', 'yellow') # set outline and fill at once down() pensize(3) forward(50) # draws a 3px-thick line @@ -253,7 +256,7 @@ isdown() # False goto(0, 0) # moves back without drawing ``` -### Drawing shapes +### Drawing shapes { cs } Shapes are drawn by moving the pen with `up()`/`down()` (pen up means move without drawing a line), `goto()`, `forward()`, and `left()`, then filling the outline with `begin_fill()`/`end_fill()`. @@ -286,7 +289,7 @@ def square(point, size, fill_color): end_fill() ``` -#### Dot +#### Dot { cs } A filled circle, built into turtle directly — no custom function needed. `dot(diameter, color)` draws it centered on wherever the pen currently is. @@ -296,7 +299,7 @@ goto(0, 0) dot(20, 'green') ``` -#### Circle +#### Circle { cs } `circle(radius)` traces an actual curved path instead of stamping an instant dot — the center ends up `radius` units to the turtle's left, and the pen itself ends up back on the circle once it's done. @@ -318,7 +321,7 @@ end_fill() circle(50, steps=6) # a hexagon ``` -#### Rectangle +#### Rectangle { cs } Same idea as `square()`, with independent width and height, drawn from a corner instead of the center — the shape a paddle or panel-style element would use. @@ -339,7 +342,7 @@ def rectangle(point, width, height, fill_color): end_fill() ``` -#### Stamping +#### Stamping { cs } When the built-in `shape()` already looks right, `stamp()` leaves a copy of it at the pen's current position — a shortcut over writing a custom drawing function like `square()` or `rectangle()`. It returns an id, so a specific stamp can be erased later with `clearstamp(stamp_id)`. @@ -349,7 +352,7 @@ goto(food) stamp_id = stamp() ``` -#### Text +#### Text { cs } `write(text)` draws a string at the pen's current position — the way a score or a message gets shown, since none of the shapes above are built for it. @@ -361,7 +364,7 @@ write('Score: 3', align='center', font=('Arial', 16, 'normal')) `align` positions the text relative to that point (`'left'`, `'center'`, or `'right'`) instead of always starting from it. Like everything else on screen, a score needs to be redrawn as part of the frame — `clear()` erases it too, so `write()` has to run again every time the score changes. -### Positions and motion +### Positions and motion { cs="Motion, Positions" } A position is two numbers, x and y. turtle represents one with **`Vec2D`**, a tuple that also supports vector arithmetic — unlike a plain tuple, adding two `Vec2D`s adds their coordinates instead of concatenating them. @@ -383,7 +386,7 @@ x, y = ball # unpack like any other tuple — 3, 5 ``` ??? tip "Reassigning from inside a function" - Reassigning a global variable's name from inside a function needs `global`, covered on [Functions](../organization/functions.md#local-vs-global-variables) — a game typically has at least one small function whose only job is reassigning a position or direction this way. *Mutating* something in place instead (`trail.append(...)`, `paddles[1] = paddles[1] + Vec2D(0, 20)`, both from "Many positions at once" below) doesn't need `global`, since the name itself is never reassigned — only reassignment does. + Reassigning a global variable's name from inside a function needs `global`, covered on [Functions](../../organization/functions.md#local-vs-global-variables) — a game typically has at least one small function whose only job is reassigning a position or direction this way. *Mutating* something in place instead (`trail.append(...)`, `paddles[1] = paddles[1] + Vec2D(0, 20)`, both from "Many positions at once" below) doesn't need `global`, since the name itself is never reassigned — only reassignment does. ```python-ref aim = Vec2D(0, -10) @@ -393,7 +396,7 @@ x, y = ball # unpack like any other tuple — 3, 5 aim = Vec2D(x, y) ``` -#### The turtle's own position { data-card-link="skip" } +#### The turtle's own position The turtle itself always knows where it is — `pos()` returns its current location as a `Vec2D`, the same type used everywhere else on this page, so a separate variable isn't strictly needed if the pen itself is what's moving. @@ -409,9 +412,9 @@ setheading(towards(ball)) forward(5) ``` -#### Many positions at once { data-card-link="skip" } +#### Many positions at once -A game's state is rarely just one lone position — a trail that grows over time, or several independent entities tracked at once. Both build on the same list/dict operations covered on [Collections](../types/collections.md). +A game's state is rarely just one lone position — a trail that grows over time, or several independent entities tracked at once. Both build on the same list/dict operations covered on [Collections](../../types/collections.md). ```python-ref trail = [Vec2D(10, 0)] @@ -428,7 +431,7 @@ paddles[1] = paddles[1] + Vec2D(0, 20) # move just one of them
    -## The game loop +## The game loop { cs="Game loop" } `ontimer(function, ms)` calls a function once, after a delay. Having that function schedule *itself* again as its last line turns a single call into a repeating loop — the heartbeat of any turtle game: move, redraw, schedule the next frame. @@ -449,7 +452,7 @@ done() # keeps the window open, listening for the scheduled calls ``` ??? tip "Spawning and removing things over time" - A loop can also grow or shrink a list of its own entities as it runs — occasionally adding a new one, and dropping ones that have drifted off-screen or otherwise stopped mattering, using the same list operations as Positions and motion's "Many positions at once". `randrange()` is from the [random](random.md) module, not turtle. + A loop can also grow or shrink a list of its own entities as it runs — occasionally adding a new one, and dropping ones that have drifted off-screen or otherwise stopped mattering, using the same list operations as Positions and motion's "Many positions at once". `randrange()` is from the [random](../utilities/random.md) module, not turtle. ```python-ref from random import randrange @@ -461,7 +464,7 @@ done() # keeps the window open, listening for the scheduled calls entities.pop(0) ``` -### done() +### done() { cs } A Python script normally runs top to bottom and exits once it reaches the last line. `done()` is always that last line — but instead of letting the script exit, it **blocks**: it hands control to the window and just sits there, waiting. @@ -475,11 +478,11 @@ While it waits, it watches for the scheduled calls and input registered earlier
    -## Input +## Input { cs } Every kind of input turtle supports works the same way: register a function once, and it gets called automatically whenever the matching event happens — nothing actually listens for anything until `done()` starts the event loop at the end of the script, so registration itself can happen in any order. -### Keyboard +### Keyboard { cs } `listen()` puts the window in a state where it's paying attention to keyboard events; `onkey(function, key)` then binds one key to a function, called with no arguments every time that key is pressed. @@ -501,7 +504,7 @@ onkey(lambda: change(-10, 0), 'Left') ??? tip "Press vs release" `onkey()` is really an alias for `onkeypress()` — a key firing the moment it's pressed down. `onkeyrelease(function, key)` is the counterpart, firing when the key comes back up instead. -### Mouse +### Mouse { cs } `onscreenclick(function)` calls a function every time the window is clicked, passing the click's x and y coordinates as arguments. @@ -520,7 +523,7 @@ onscreenclick(tap) ??? tip "Dragging and releasing" `ondrag(function)` calls a function repeatedly, passed the pointer's x/y, while the mouse moves with the button held down — for something dragged around rather than tapped. `onrelease(function)` is the counterpart to `onscreenclick()`, firing when a click ends instead of when it starts. -### Dialog prompts +### Dialog prompts { cs } `textinput(title, prompt)` and `numinput(title, prompt)` pop up a small dialog box asking for a string or a number, returning what the player typed (or `None` if they cancelled). It's a separate native window, centered over the game window rather than drawn on the canvas. @@ -535,7 +538,7 @@ lives = numinput('Lives', 'How many lives?', default=3, minval=1, maxval=5)
    -## Detecting collisions +## Detecting collisions { cs="inside" } Many games reduce to the same question: is this position touching that one? @@ -546,7 +549,7 @@ def inside(point): return -200 < x < 200 and -200 < y < 200 ``` -### Distance +### Distance { cs } `abs()` on a `Vec2D` returns its length — subtracting two positions first gives the distance between them, without writing out a square root by hand. @@ -555,7 +558,7 @@ paddle = Vec2D(-200, 0) close_enough = abs(ball - paddle) < 15 ``` -### Overlap +### Overlap { cs } Checking whether a point falls within a range — a paddle's height, say — is a plain comparison, no vector math needed. @@ -568,7 +571,7 @@ high = paddle_y + 50 touching = low <= ball_y <= high ``` -### Membership +### Membership { cs } A position can also collide with itself — checking whether it already appears somewhere in a list of positions, the same `in` used for any other membership check. @@ -581,7 +584,7 @@ crashed = head in trail
    -## Common patterns +## Common patterns { cs } Every block above is a small, general-purpose piece. Combined, a few recurring shapes cover most simple games — each sketched below as pseudocode, the shape to fill in with real building blocks from the sections above. @@ -703,7 +706,7 @@ Mixing and matching these — a controlled object *and* a growing trail, say, or
    -## More advanced games { data-card-link="skip" } +## More advanced games turtle's window and shapes are enough for something like snake, flappy, or pong, but not for much more — no sprites, no sound, no real physics. For anything more advanced, [pygame](https://www.pygame.org/docs/) and [arcade](https://api.arcade.academy/) are the two most common next steps; both have their own official documentation, linked above. diff --git a/docs/libraries/pillow.md b/docs/libraries/images/pillow.md similarity index 89% rename from docs/libraries/pillow.md rename to docs/libraries/images/pillow.md index a716449..62a253d 100644 --- a/docs/libraries/pillow.md +++ b/docs/libraries/images/pillow.md @@ -1,4 +1,7 @@ --- +cheatsheet_title: Pillow +cheatsheet_description: Opening, editing, and saving images, built around one Image object. +cheatsheet_title_suffix: :material-download-outline:{ .library-badge .library-badge--third-party title="Third-party — install separately with pip" } description: >- Opening, editing, and saving images in Python with Pillow: resizing, cropping, drawing, filters, and format conversion. @@ -12,13 +15,13 @@ description: >- Pillow is an open-source project maintained by volunteer contributors. -**Pillow** (imported as `PIL`) is a popular library for opening, editing, and saving image files — photos, screenshots, thumbnails, anything in a common format like JPEG or PNG. It's a third-party package, not part of the standard library, but it's the de facto standard for image work in Python. Like [Tkinter](tkinter.md), Pillow ultimately produces visual output — a saved or displayed image — which can't be shown inside this site's browser sandbox, so the examples below aren't runnable here. Copy them into a local `.py` file alongside an image and run them with `python` to see the results. +**Pillow** (imported as `PIL`) is a popular library for opening, editing, and saving image files — photos, screenshots, thumbnails, anything in a common format like JPEG or PNG. It's a third-party package, not part of the standard library, but it's the de facto standard for image work in Python. Like [Tkinter](../desktop_uis/tkinter.md), Pillow ultimately produces visual output — a saved or displayed image — which can't be shown inside this site's browser sandbox, so the examples below aren't runnable here. Copy them into a local `.py` file alongside an image and run them with `python` to see the results.
    -## Setup { data-card-link="skip" } +## Setup ```bash pip install pillow @@ -34,7 +37,7 @@ from PIL import Image
    -## Why Pillow? +## Why Pillow? { cs="why Pillow?" } Pillow is the direct successor to PIL (the original Python Imaging Library, now unmaintained), and has become the standard way to work with images in Python — resizing thumbnails, converting formats, watermarking, or feeding images into a machine learning pipeline. It wraps all of this in one consistent `Image` object, so once you know how to open, transform, and save an image, the same handful of methods carry over to almost any task. @@ -76,11 +79,11 @@ Beyond the base [`Image`](#the-image) object, Pillow's functionality is spread a
    -## The Image +## The Image { cs="Image" } The `Image` object is where every Pillow workflow starts and ends — opening a file, transforming it, and saving the result all happen through methods on this one class. -### Opening and saving images +### Opening and saving images { cs="opening and saving images" } Every Pillow workflow starts the same way: open a file into an `Image` object, do something to it, then save the result — Pillow infers the file format from the extension you save to, so converting formats is often just a matter of changing the file extension. @@ -138,7 +141,7 @@ img.save("snake_copy.png") img.show() ``` -### Basic operations +### Basic operations { cs="basic operations" } Pillow's core editing operations — resizing, cropping, rotating, flipping — are all methods on an `Image` that return a *new* `Image`, leaving the original untouched. @@ -148,7 +151,7 @@ cropped = img.crop((0, 0, 200, 200)) rotated = img.rotate(90) ``` -#### Resize +#### Resize { cs="resize" } Scales the image to an exact new size. `.resize((width, height))` doesn't preserve the original aspect ratio for you, so stretching happens if the new dimensions don't match the original proportions. For a quick, ratio-preserving thumbnail instead, use `.thumbnail((max_width, max_height))`, which resizes in place rather than returning a new image. @@ -165,7 +168,7 @@ thumbnail = img.resize((200, 150)) print(thumbnail.size) ``` -#### Crop +#### Crop { cs="crop" } Takes a bounding box and returns just that rectangular region. `.crop()` takes `(left, upper, right, lower)` pixel coordinates. `(0, 0)` is the top-left corner of the image, with `x` increasing rightward and `y` increasing downward. @@ -182,7 +185,7 @@ cropped = img.crop((50, 50, 250, 200)) print(cropped.size) ``` -#### Rotate and flip +#### Rotate and flip { cs="rotate and flip" } `.rotate(degrees)` rotates counter-clockwise around the image's center. Pass `expand=True` to grow the canvas so corners aren't clipped off (without it, the image keeps its original size and rotated corners are cropped away). `.transpose()` handles flips and 90°-multiple rotations without any clipping concerns, using constants like `Image.FLIP_LEFT_RIGHT` or `Image.ROTATE_90`. @@ -200,7 +203,7 @@ flipped = img.transpose(Image.FLIP_LEFT_RIGHT) print(rotated.size, flipped.size) ``` -### Image modes +### Image modes { cs="image modes" } An image's **mode** determines how each pixel's color is stored — how many bands it has and what each one means. Converting between modes is a single method call, and it's often a required first step before an operation that only works on one mode (like grayscale-only filters). `.convert(mode)` returns a new image re-encoded into the given mode — `"L"` collapses color down to a single grayscale band; `"RGBA"` adds an alpha (transparency) band on top of red/green/blue, where `0` is fully transparent and `255` is fully opaque. @@ -223,7 +226,7 @@ print(grayscale.mode, rgba.mode)
    -## ImageOps module +## ImageOps module { cs="ImageOps" } The `ImageOps` module collects common one-line transforms that would otherwise take several steps to write by hand — contrast fixes, mirroring, and color inversion among them. @@ -234,7 +237,7 @@ fixed = ImageOps.autocontrast(img) mirrored = ImageOps.mirror(img) ``` -### Common ImageOps functions +### Common ImageOps functions { cs="common ImageOps functions" } `.autocontrast()` stretches an image's darkest and lightest pixels out to pure black and white, which can fix a flat, washed-out photo without manually tuning `ImageEnhance.Contrast`. `.mirror()`/`.flip()` cover the same ground as `.transpose()` with more direct names. `.invert()` flips every pixel to its opposite color — it only works on `"RGB"` (or `"L"`) images, so convert first if the source has an alpha band. @@ -258,7 +261,7 @@ fixed.save("fixed.jpg")
    -## ImageDraw module +## ImageDraw module { cs="ImageDraw" } An `Image` object is really just a grid of pixel values — it has no drawing tools of its own. `ImageDraw` is the first example of a **companion module**: a separate class that wraps an `Image` and adds one specific ability, here turning it into a canvas you can draw directly onto — shapes and lines, useful for annotating a photo or generating an image from scratch rather than editing an existing file. `ImageFont`, `ImageFilter`, `ImageEnhance`, `ImageOps`, and the modules further down this page all follow the same pattern: they act on an `Image` from the outside, rather than `Image` itself growing a method for everything. @@ -270,7 +273,7 @@ draw.rectangle((10, 10, 100, 60), outline="green", width=3) draw.text((15, 20), "ball python", fill="green") ``` -### Shapes and lines +### Shapes and lines { cs="shapes and lines" } `ImageDraw.Draw(img)` creates a drawing context bound to an image. Every call on it modifies `img` directly, in place. `.rectangle()`, `.ellipse()`, and `.line()` each take a bounding box or set of coordinates, plus `outline`/`fill` colors and an optional `width`. @@ -326,7 +329,7 @@ img.save("shapes.png") ``` ??? tip "Drawing with objects" - Once a drawing gets complicated, it's common to wrap each thing you're drawing in its own class — an object that stores its own position/size/color, and knows how to draw itself given a drawing context. Nothing here is Pillow-specific: it's the same pattern covered in [Classes](../organization/classes.md) — bundling data with the behavior that acts on it — just applied to a shape instead of a snake. A calling function loops over a list of these objects and calls `.draw()` on each, so building a complex image — dozens of randomly placed shapes, say, using the `random` module — is just a loop appending new `Shape` objects rather than dozens of manual `draw_context` calls. + Once a drawing gets complicated, it's common to wrap each thing you're drawing in its own class — an object that stores its own position/size/color, and knows how to draw itself given a drawing context. Nothing here is Pillow-specific: it's the same pattern covered in [Classes](../../organization/classes.md) — bundling data with the behavior that acts on it — just applied to a shape instead of a snake. A calling function loops over a list of these objects and calls `.draw()` on each, so building a complex image — dozens of randomly placed shapes, say, using the `random` module — is just a loop appending new `Shape` objects rather than dozens of manual `draw_context` calls. ```python-ref class Shape: @@ -380,7 +383,7 @@ img.save("shapes.png")
    -## ImageFont module +## ImageFont module { cs="ImageFont" } `ImageDraw.text()` works with no extra setup, but falls back to a small built-in bitmap font. `ImageFont` loads an actual `.ttf` font file at a chosen size, for anything larger or more legible. @@ -391,7 +394,7 @@ font = ImageFont.truetype("arial.ttf", 20) draw.text((10, 10), "burmese python", fill="black", font=font) ``` -### Loading a font +### Loading a font { cs="loading a font" } Loads a `.ttf` (or `.otf`) font file at a specific point size. `ImageFont.truetype(path, size)` returns a font object to pass into `draw.text(..., font=font)`. The path can be a font file sitting next to your script, or a system font's full path — sizes aren't interchangeable between fonts, so reload at a new size rather than trying to scale a loaded font after the fact. @@ -414,7 +417,7 @@ img.save("labeled.png")
    -## ImageColor module +## ImageColor module { cs="ImageColor" } Drawing methods accept a color as a plain name (`"green"`) or a hex string (`"#3f6b52"`), but sometimes you need that same color as an actual `(r, g, b)` tuple — to do math on it, blend it with another color, or store it in a data structure like the `Shape` class above. `ImageColor.getrgb()` converts either format into the tuple Pillow uses internally. @@ -425,7 +428,7 @@ rgb = ImageColor.getrgb("green") # (0, 128, 0) rgb2 = ImageColor.getrgb("#3f6b52") # (63, 107, 82) ``` -### Converting color names +### Converting color names { cs="converting color names" } Accepts most CSS-style color names and `#rrggbb`/`#rgb` hex strings, returning a plain `(r, g, b)` tuple. `.getrgb()` returns `(r, g, b, a)` if the input included transparency. Useful once a palette is defined as hex codes rather than named colors, or when a color needs to be manipulated as numbers rather than passed straight into a drawing method. @@ -447,7 +450,7 @@ print(green_rgb, hex_rgb)
    -## ImageFilter module +## ImageFilter module { cs="ImageFilter" } Beyond geometric edits, `ImageFilter` can adjust an image's *look* — blurring, sharpening, or tracing its edges — by applying a ready-made pixel transformation, no convolution or kernel math required. @@ -457,7 +460,7 @@ from PIL import ImageFilter blurred = img.filter(ImageFilter.BLUR) ``` -### Applying a filter +### Applying a filter { cs="applying a filter" } Applies one of Pillow's built-in filter presets, each a ready-made pixel transformation. `.filter()` — `ImageFilter.CONTOUR` traces edges into a sketch-like outline, distinct from `FIND_EDGES`, which highlights edges while keeping the rest of the image dark. @@ -484,7 +487,7 @@ outlined.save("outlined.jpg")
    -## ImageEnhance module +## ImageEnhance module { cs="ImageEnhance" } Where `ImageFilter` applies a fixed preset, `ImageEnhance` lets you dial an existing quality — brightness, contrast, color, sharpness — up or down by an exact amount. @@ -494,7 +497,7 @@ from PIL import ImageEnhance brighter = ImageEnhance.Brightness(img).enhance(1.5) ``` -### Enhancing an image +### Enhancing an image { cs="enhancing an image" } Each `ImageEnhance` class wraps an image and exposes `.enhance(factor)`. `Brightness`, `Contrast`, `Color`, `Sharpness` — `1.0` leaves the image unchanged, below `1.0` reduces the effect, and above `1.0` increases it. `Color` controls saturation specifically: pushed toward `0.0` the image slides to grayscale, pushed well above `1.0` colors become more vivid and saturated. @@ -519,7 +522,7 @@ more_colorful.save("more_colorful.jpg")
    -## ImageChops module +## ImageChops module { cs="ImageChops" } Everything so far transforms a *single* image. `ImageChops` ("channel operations") instead combines two images of the same size, pixel by pixel — spotting what changed between two photos, or blending one image into another. @@ -530,7 +533,7 @@ diff = ImageChops.difference(before, after) blended = ImageChops.multiply(img, mask) ``` -### Comparing and combining images +### Comparing and combining images { cs="comparing and combining images" } `.difference(im1, im2)` subtracts one image from the other pixel by pixel. Identical areas come out solid black, and anything that changed shows up as a bright patch. Calling `.getbbox()` on the result gives the bounding box of everything that differs (or `None` if the two images are pixel-for-pixel identical), a quick way to check "did anything change?" without comparing every pixel yourself. `.multiply()`/`.screen()`/`.add()` combine two images with different blending math, similar to layer blend modes in photo-editing software. @@ -554,7 +557,7 @@ diff.save("diff.jpg")
    -## Format conversion +## Format conversion { cs="convert" } Because `.save()` infers the output format from the file extension, converting between formats is usually just an open-then-save with a different name — with a couple of format-specific details worth knowing. @@ -563,7 +566,7 @@ img = Image.open("snake.png") img.convert("RGB").save("snake.jpg") # JPEG has no transparency, so drop RGBA first ``` -### Converting between formats +### Converting between formats { cs="converting between formats" } JPEG doesn't support transparency, so saving an `"RGBA"` image straight to `.jpg` raises an error. Convert to `"RGB"` first, which drops the alpha band. PNG, by contrast, supports both `"RGB"` and `"RGBA"` natively, so no conversion is needed going the other direction. @@ -583,9 +586,9 @@ img.convert("RGB").save("snake.jpg")
    -## ImageSequence module +## ImageSequence module { cs="ImageSequence" } -An animated GIF is really a whole stack of images shown one after another. `Image.open()` only gives you the first frame by default — `ImageSequence` lets a [`for` loop](../flow/loops.md) step through every frame in order. +An animated GIF is really a whole stack of images shown one after another. `Image.open()` only gives you the first frame by default — `ImageSequence` lets a [`for` loop](../../flow/loops.md) step through every frame in order. ```python-ref from PIL import Image, ImageSequence @@ -595,7 +598,7 @@ for frame in ImageSequence.Iterator(gif): frame.save(f"frame_{frame.tell()}.png") ``` -### Looping over GIF frames +### Looping over GIF frames { cs="looping over GIF frames" } Hands a `for` loop one frame at a time, in order, from an animated image. `ImageSequence.Iterator(img)` — each frame is a regular `Image` object, so every operation covered on this page (resize, filter, draw) works on it the same way. `.tell()` reports which frame number you're currently on, useful for numbering saved output files. @@ -617,9 +620,9 @@ for frame in ImageSequence.Iterator(gif):
    -## Putting it together +## Putting it together { cs="putting it together" } -Pillow doesn't need anything special to combine with the rest of Python — a function wrapping one transformation, called from an `if`/`elif` chosen by [user input](../flow/conditionals.md), looped until the user's done, is enough to build a small interactive tool out of the operations above. +Pillow doesn't need anything special to combine with the rest of Python — a function wrapping one transformation, called from an `if`/`elif` chosen by [user input](../../flow/conditionals.md), looped until the user's done, is enough to build a small interactive tool out of the operations above. ```python-ref def apply_filter(img, choice): @@ -633,9 +636,9 @@ def apply_filter(img, choice): return img ``` -### An interactive filter tool +### An interactive filter tool { cs="an interactive filter tool" } -Combines a function, an `if`/`elif` chain, and a `while` loop — nothing here is Pillow-specific. Each piece here is something covered elsewhere on this site — a [function](../organization/functions.md) wrapping one transformation, an [`if`/`elif` chain](../flow/conditionals.md) picking which one to run, and a [`while` loop](../flow/loops.md#while-loops) that keeps asking until the user's satisfied. Pillow itself only shows up inside `apply_filter`. +Combines a function, an `if`/`elif` chain, and a `while` loop — nothing here is Pillow-specific. Each piece here is something covered elsewhere on this site — a [function](../../organization/functions.md) wrapping one transformation, an [`if`/`elif` chain](../../flow/conditionals.md) picking which one to run, and a [`while` loop](../../flow/loops.md#while-loops) that keeps asking until the user's satisfied. Pillow itself only shows up inside `apply_filter`. ```python-ref while True: diff --git a/docs/libraries/index.md b/docs/libraries/index.md index 5822d41..f7e3b46 100644 --- a/docs/libraries/index.md +++ b/docs/libraries/index.md @@ -10,497 +10,4 @@ hide: Libraries allow us to apply Python to real tasks. These are a few popular ones, but there are many. -
    - -
    -#### Utilities { .pt-homepage-heading } - -
    - -- :material-format-list-group:{ .lg .middle } [__collections__](collections.md) -[:material-language-python:](collections.md){ .pt-lib-badge .pt-lib-badge--builtin title="Built-in — included with Python" } - {: data-fcm-hide="essentials" } - - Specialized containers: counting items, grouping with defaults, named tuples, fast queues. - - [**`Counter`**](collections.md#counter): - [`+ - & |`](collections.md#combine) - [`counts[item]`](collections.md#count) - [`elements`](collections.md#inspect) - [`most_common`](collections.md#count) - [`subtract`](collections.md#update) - [`total`](collections.md#count) - [`update`](collections.md#update) - - [**`defaultdict`**](collections.md#defaultdict): - [`default_factory`](collections.md#defaultdict) - [`get`](collections.md#reading-vs-writing) - - [**`namedtuple`**](collections.md#namedtuple): - [`_asdict`](collections.md#convert) - [`_field_defaults`](collections.md#inspect_1) - [`_fields`](collections.md#inspect_1) - [`_make`](collections.md#create) - [`_replace`](collections.md#convert) - [`defaults=`](collections.md#create) - - [**`deque`**](collections.md#deque): - [`append`](collections.md#add) - [`appendleft`](collections.md#add) - [`clear`](collections.md#remove) - [`copy`](collections.md#inspect_2) - [`count`](collections.md#inspect_2) - [`extend`](collections.md#add) - [`extendleft`](collections.md#add) - [`index`](collections.md#inspect_2) - [`insert`](collections.md#add) - [`maxlen=`](collections.md#reorder) - [`pop`](collections.md#remove) - [`popleft`](collections.md#remove) - [`remove`](collections.md#remove) - [`reverse`](collections.md#reorder) - [`rotate`](collections.md#reorder) - - [**`OrderedDict`**](collections.md#ordereddict): - [`==`](collections.md#compare) - [`move_to_end`](collections.md#reorder_1) - [`popitem`](collections.md#reorder_1) - - [**`ChainMap`**](collections.md#chainmap): - [`maps`](collections.md#inspect_3) - [`new_child`](collections.md#extend) - [`parents`](collections.md#inspect_3) - - [**`User* wrapper`**](collections.md#user-wrapper-classes): - [`UserDict`](collections.md#user-wrapper-classes) - [`UserList`](collections.md#user-wrapper-classes) - [`UserString`](collections.md#user-wrapper-classes) - -- :material-calendar-clock:{ .lg .middle } [__datetime__](datetime.md) -[:material-language-python:](datetime.md){ .pt-lib-badge .pt-lib-badge--builtin title="Built-in — included with Python" } - - Calculating and formatting dates and times. - - [`creating a specific date`](datetime.md#creating-a-specific-date) - [`date`](datetime.md#creating-dates-and-times) - [`strftime`](datetime.md#formatting-with-strftime) - - [`difference between two dates`](datetime.md#difference-between-two-dates) - [`strptime`](datetime.md#parsing-a-string-with-strptime) - [`timedelta`](datetime.md#date-arithmetic) - -- :material-square-root-box:{ .lg .middle } [__math__](math.md) -[:material-language-python:](math.md){ .pt-lib-badge .pt-lib-badge--builtin title="Built-in — included with Python" } - - Rounding, roots, constants, and logarithms. - - [**`floor`**](math.md#rounding): - [`ceil`](math.md#rounding) - [`trunc`](math.md#trunc) - - [**`sqrt`**](math.md#roots-and-powers): - [`pow`](math.md#pow) - [`isqrt`](math.md#pow) - - [**`pi`**](math.md#constants): - [`inf`](math.md#constants) - [`nan`](math.md#constants) - - [**`log2`**](math.md#logarithms): - [`log10`](math.md#logarithms) - [`exp`](math.md#logarithms) - - [**`isclose`**](math.md#comparing-floats) - -- :material-dice-multiple:{ .lg .middle } [__random__](random.md) -[:material-language-python:](random.md){ .pt-lib-badge .pt-lib-badge--builtin title="Built-in — included with Python" } - - Random numbers, random picks, shuffled order. - - [**`randint`**](random.md#random-numbers) - - [**`choice`**](random.md#random-selections): - [`sample`](random.md#sampling-without-replacement) - [`shuffle`](random.md#shuffling-a-list) - -- :material-text-search:{ .lg .middle } [__re__](re.md) -[:material-language-python:](re.md){ .pt-lib-badge .pt-lib-badge--builtin title="Built-in — included with Python" } - - Regular expressions: searching, extracting, and replacing text by pattern. - - [**`search`**](re.md#searching-for-a-pattern): - [`compile`](re.md#searching-for-a-pattern) - - [**`findall`**](re.md#finding-all-matches) - - [**`groups`**](re.md#groups): - [`named groups`](re.md#groups) - - [**`sub`**](re.md#replacing-text) - - [**`split`**](re.md#splitting-on-a-pattern) - -- :material-clock-outline:{ .lg .middle } [__time__](time.md) -[:material-language-python:](time.md){ .pt-lib-badge .pt-lib-badge--builtin title="Built-in — included with Python" } - - Reading the system clock, pausing execution, and measuring elapsed time. - - [**`time`**](time.md#reading-the-clock) - - [**`sleep`**](time.md#pausing-execution) - - [**`perf_counter`**](time.md#measuring-elapsed-time) - - [**`localtime`**](time.md#formatting-the-current-time): - [`strftime`](time.md#formatting-the-current-time) - -
    -
    - -
    -#### Data analysis { .pt-homepage-heading } - -
    - -- :material-file-delimited-outline:{ .lg .middle } [__csv__](csv.md) -[:material-language-python:](csv.md){ .pt-lib-badge .pt-lib-badge--builtin title="Built-in — included with Python" } - - Reading and writing spreadsheets. - - [`writer`](csv.md#writing-csv-files) - - [`DictReader`](csv.md#reading-rows-as-dictionaries) - [`reader`](csv.md#reading-csv-files) - -- :material-chart-line:{ .lg .middle } [__matplotlib__](matplotlib.md) -[:material-download-outline:](matplotlib.md){ .pt-lib-badge .pt-lib-badge--third-party title="Third-party — install separately with pip" } - - Charts and plots: line, bar, and scatter, built directly from plain Python data. - - [**`line plots`**](matplotlib.md#line-plots): - [`labels and title`](matplotlib.md#labels-and-title) - [`multiple lines and a legend`](matplotlib.md#multiple-lines-and-a-legend) - - [**`bar charts`**](matplotlib.md#bar-charts) - - [**`scatter plots`**](matplotlib.md#scatter-plots) - - [**`subplots`**](matplotlib.md#subplots) - - [**`saving a figure`**](matplotlib.md#saving-a-figure) - -- :material-matrix:{ .lg .middle } [__NumPy__](numpy.md) -[:material-download-outline:](numpy.md){ .pt-lib-badge .pt-lib-badge--third-party title="Third-party — install separately with pip" } - {: data-fcm-hide="essentials" } - - Fast numeric arrays, with math applied to a whole array at once instead of item by item. - - [**`array operations`**](numpy.md#array-operations): - [`boolean mask`](numpy.md#filtering-with-a-boolean-mask) - [`mean`](numpy.md#aggregating-an-array) - - [`arange`](numpy.md#building-arrays-without-a-list) - [`ndarray`](numpy.md#creating-arrays) - -- :material-table:{ .lg .middle } [__pandas__](pandas.md) -[:material-download-outline:](pandas.md){ .pt-lib-badge .pt-lib-badge--third-party title="Third-party — install separately with pip" } - {: data-fcm-hide="essentials" } - - Tabular data: rows and columns, like a spreadsheet, built on top of NumPy. - - [**`DataFrame`**](pandas.md#building-a-dataframe) - - [**`working with a DataFrame`**](pandas.md#working-with-a-dataframe): - [`mean`](pandas.md#summarizing-a-column) - [`sort_values`](pandas.md#sorting-rows) - -
    -
    - -
    -#### APIs { .pt-homepage-heading } - -
    - -- :material-code-json:{ .lg .middle } [__json__](json.md) -[:material-language-python:](json.md){ .pt-lib-badge .pt-lib-badge--builtin title="Built-in — included with Python" } - - Reading and writing JSON data: nested dicts and lists, saved to a file or a string. - - [`dump`](json.md#writing-json-files) - - [`load`](json.md#reading-json-files) - [`nested data`](json.md#nested-data) - - [`loads`](json.md#working-with-strings-instead-of-files) - -- :material-webhook:{ .lg .middle } [__requests__](requests.md) -[:material-download-outline:](requests.md){ .pt-lib-badge .pt-lib-badge--third-party title="Third-party — install separately with pip" } - - Fetching data over the internet, like asking a website or API for information. - - [**`how requests work`**](requests.md#how-a-request-works) - - [**`get`**](requests.md#making-a-request): - [`headers`](requests.md#custom-headers) - [`json`](requests.md#parsing-json) - [`params`](requests.md#query-parameters) - [`status_code`](requests.md#checking-the-status-code) - - [**`post`**](requests.md#sending-data) - - [**`error handling`**](requests.md#handling-request-errors) - -
    -
    - -
    -#### Web scraping { .pt-homepage-heading } - -
    - -- :material-pot-steam-outline:{ .lg .middle } [__BeautifulSoup__](beautifulsoup.md) -[:material-download-outline:](beautifulsoup.md){ .pt-lib-badge .pt-lib-badge--third-party title="Third-party — install separately with pip" } - - Parsing HTML: finding tags, reading attributes and text, and turning a page into structured data. - - [**`HTML and web pages`**](beautifulsoup.md#html-and-web-pages) - - [**`Parsing HTML`**](beautifulsoup.md#parsing-html): - [`Finding tags`](beautifulsoup.md#finding-tags) - [`Reading text and attributes`](beautifulsoup.md#reading-text-and-attributes) - - [**`Extracting structured data`**](beautifulsoup.md#extracting-structured-data) - - [**`Putting it together`**](beautifulsoup.md#putting-it-together): - [`Common tasks`](beautifulsoup.md#common-tasks) - [`Scraping a real page`](beautifulsoup.md#scraping-a-real-page) - -
    -
    - -
    -#### Image editing { .pt-homepage-heading } - -
    - -- :material-image-outline:{ .lg .middle } [__Pillow__](pillow.md) -[:material-download-outline:](pillow.md){ .pt-lib-badge .pt-lib-badge--third-party title="Third-party — install separately with pip" } - - Opening, editing, and saving images, built around one Image object. - - [**`why Pillow?`**](pillow.md#why-pillow) - - [**`Image`**](pillow.md#the-image): - [`basic operations`](pillow.md#basic-operations) - [`crop`](pillow.md#crop) - [`image modes`](pillow.md#image-modes) - [`opening and saving images`](pillow.md#opening-and-saving-images) - [`resize`](pillow.md#resize) - [`rotate and flip`](pillow.md#rotate-and-flip) - - [**`ImageOps`**](pillow.md#imageops-module): - [`common ImageOps functions`](pillow.md#common-imageops-functions) - - [**`ImageDraw`**](pillow.md#imagedraw-module): - [`shapes and lines`](pillow.md#shapes-and-lines) - - [**`ImageFont`**](pillow.md#imagefont-module): - [`loading a font`](pillow.md#loading-a-font) - - [**`ImageColor`**](pillow.md#imagecolor-module): - [`converting color names`](pillow.md#converting-color-names) - - [**`ImageFilter`**](pillow.md#imagefilter-module): - [`applying a filter`](pillow.md#applying-a-filter) - - [**`ImageEnhance`**](pillow.md#imageenhance-module): - [`enhancing an image`](pillow.md#enhancing-an-image) - - [**`ImageChops`**](pillow.md#imagechops-module): - [`comparing and combining images`](pillow.md#comparing-and-combining-images) - - [**`convert`**](pillow.md#format-conversion): - [`converting between formats`](pillow.md#converting-between-formats) - - [**`ImageSequence`**](pillow.md#imagesequence-module): - [`looping over GIF frames`](pillow.md#looping-over-gif-frames) - - [**`putting it together`**](pillow.md#putting-it-together): - [`an interactive filter tool`](pillow.md#an-interactive-filter-tool) - -
    -
    - -
    -#### Desktop UIs { .pt-homepage-heading } - -
    - -- :material-application-outline:{ .lg .middle } [__Tkinter__](tkinter.md) -[:material-language-python:](tkinter.md){ .pt-lib-badge .pt-lib-badge--builtin title="Built-in — included with Python" } - - Creating desktop applications: text, buttons, dropdowns, forms, output, etc. - - [**`Tk`**](tkinter.md#creating-a-window) - - [**`Button`**](tkinter.md#widgets): - [`Button`](tkinter.md#button) - [`Entry`](tkinter.md#entry) - [`Label`](tkinter.md#label) - - [**`pack`**](tkinter.md#layout-managers): - [`grid`](tkinter.md#grid) - [`pack`](tkinter.md#pack) - - [**`configure`**](tkinter.md#configuring-widgets): - [`reading and changing options`](tkinter.md#reading-and-changing-options) - - [**`command`**](tkinter.md#handling-events): - [`binding events`](tkinter.md#binding-events) - [`command callbacks`](tkinter.md#command-callbacks) - - [**`Style`**](tkinter.md#styling-with-ttk): - [`customizing a style`](tkinter.md#customizing-a-style) - - [**`messagebox`**](tkinter.md#dialogs): - [`file dialogs`](tkinter.md#file-dialogs) - [`message boxes`](tkinter.md#message-boxes) - - [**`winfo_width`**](tkinter.md#introspecting-widgets): - [`winfo methods`](tkinter.md#winfo-methods) - - [**`putting it together`**](tkinter.md#putting-it-together): - [`a simple form`](tkinter.md#a-simple-form) - -
    -
    - -
    -#### Games { .pt-homepage-heading } - -
    - -- :material-turtle:{ .lg .middle } [__turtle__](turtle.md) -[:material-language-python:](turtle.md){ .pt-lib-badge .pt-lib-badge--builtin title="Built-in — included with Python" } - - Build small movement-based games with a pen cursor. - - [**`Concepts`**](turtle.md#concepts) - - [**`Screen`**](turtle.md#the-screen): - [`Background`](turtle.md#background) - [`Clear`](turtle.md#clear-screen) - [`Close`](turtle.md#closing-the-window) - [`Colors`](turtle.md#colors) - [`Setup`](turtle.md#screen-setup) - - [**`turtle cursor`**](turtle.md#the-turtle-cursor): - [`Appearance`](turtle.md#shape) - [`Circle`](turtle.md#circle) - [`Color`](turtle.md#shape-color-size) - [`Custom images`](turtle.md#custom-images) - [`Dot`](turtle.md#dot) - [`Drawing shapes`](turtle.md#drawing-shapes) - [`Hide`](turtle.md#show-or-hide) - [`Ink`](turtle.md#ink) - [`Motion`](turtle.md#positions-and-motion) - [`Positions`](turtle.md#positions-and-motion) - [`Rectangle`](turtle.md#rectangle) - [`Shape`](turtle.md#shape-color-size) - [`Show`](turtle.md#show-or-hide) - [`Size`](turtle.md#shape-color-size) - [`Stamping`](turtle.md#stamping) - [`Text`](turtle.md#text) - [`Tracer`](turtle.md#trace-movement) - - [**`Game loop`**](turtle.md#the-game-loop): - [`done()`](turtle.md#done) - - [**`Input`**](turtle.md#input): - [`Dialog prompts`](turtle.md#dialog-prompts) - [`Keyboard`](turtle.md#keyboard) - [`Mouse`](turtle.md#mouse) - - [**`inside`**](turtle.md#detecting-collisions): - [`Distance`](turtle.md#distance) - [`Membership`](turtle.md#membership) - [`Overlap`](turtle.md#overlap) - - [**`Common patterns`**](turtle.md#common-patterns) - -
    -
    - -
    -#### Testing { .pt-homepage-heading } - -
    - -- :material-test-tube:{ .lg .middle } [__pytest__](pytest.md) -[:material-download-outline:](pytest.md){ .pt-lib-badge .pt-lib-badge--third-party title="Third-party — install separately with pip" } - - Writing and running tests: assertions, fixtures, and parametrizing. - - [**`writing and running a test`**](pytest.md#writing-and-running-a-test): - [`from the command line`](pytest.md#from-the-command-line) - - [**`reading a failure`**](pytest.md#reading-a-failure) - - [**`fixtures`**](pytest.md#fixtures) - - [**`parametrizing tests`**](pytest.md#parametrizing-tests) - - [**`testing for exceptions`**](pytest.md#testing-for-exceptions) - -
    -
    - -
    -#### Computer vision { .pt-homepage-heading } - -
    - -- :material-face-recognition:{ .lg .middle } [__OpenCV__](opencv.md) -[:material-download-outline:](opencv.md){ .pt-lib-badge .pt-lib-badge--third-party title="Third-party — install separately with pip" } - {: data-fcm-hide="essentials" } - - Real-time image and video analysis, built directly on NumPy arrays: color spaces, edge detection, face detection. - - [**`reading, displaying, saving images`**](opencv.md#reading-displaying-and-saving-images): - [`displaying a window`](opencv.md#displaying-a-window) - [`imread`](opencv.md#reading-a-file) - [`saving a file`](opencv.md#saving-a-file) - - [**`drawing`**](opencv.md#drawing-shapes-and-text): - [`shapes and lines`](opencv.md#shapes-and-lines) - [`text`](opencv.md#text) - - [**`color spaces`**](opencv.md#color-spaces): - [`cvtColor`](opencv.md#converting-color-spaces) - - [**`CascadeClassifier`**](opencv.md#face-detection-with-cascade-classifiers): - [`detecting and labeling faces`](opencv.md#detecting-and-labeling-faces) - - [**`VideoCapture`**](opencv.md#working-with-video): - [`reading frames`](opencv.md#reading-frames) - - [**`basic operations`**](opencv.md#basic-operations): - [`cropping`](opencv.md#cropping) - [`resize`](opencv.md#resize) - [`rotating`](opencv.md#rotating) - - [**`thresholding, edge detection`**](opencv.md#thresholding-and-edge-detection): - [`Canny`](opencv.md#edge-detection) - [`threshold`](opencv.md#threshold) - - [**`blurring`**](opencv.md#blurring): - [`gaussian blur`](opencv.md#gaussian-blur) - - [**`contours`**](opencv.md#contours): - [`finding and drawing contours`](opencv.md#finding-and-drawing-contours) - -
    -
    - -
    + diff --git a/docs/libraries/pytest.md b/docs/libraries/testing/pytest.md similarity index 90% rename from docs/libraries/pytest.md rename to docs/libraries/testing/pytest.md index ad248a0..ba7acf0 100644 --- a/docs/libraries/pytest.md +++ b/docs/libraries/testing/pytest.md @@ -1,4 +1,7 @@ --- +cheatsheet_title: pytest +cheatsheet_description: 'Writing and running tests: assertions, fixtures, and parametrizing.' +cheatsheet_title_suffix: :material-download-outline:{ .library-badge .library-badge--third-party title="Third-party — install separately with pip" } description: >- Writing and running tests in Python with pytest: assertions, fixtures, parametrizing, and testing for exceptions, with runnable examples. @@ -18,7 +21,7 @@ pytest is an open-source project maintained by volunteer contributors.
    -## Setup { data-card-link="skip" } +## Setup ```bash pip install pytest @@ -42,7 +45,7 @@ import pytest
    -## Writing and running a test +## Writing and running a test { cs="writing and running a test" } A pytest test is an ordinary function, named `test_...`, that makes one or more `assert` statements about the code it's checking. No import, base class, or naming beyond the `test_` prefix is required. @@ -52,7 +55,7 @@ def test_species_count(): assert len(species) == 3 ``` -### From the command line +### From the command line { cs="from the command line" } Normally you run `pytest` (or `python -m pytest`) from a terminal in the project directory, and it discovers every `test_*.py` file on its own — no need to name each one. This page's sandbox has no terminal, so the examples below call `pytest.main()` directly instead, which does the same discovery-and-run programmatically. @@ -82,7 +85,7 @@ pytest.main(["-v", "test_snakes.py"])
    -## Reading a failure +## Reading a failure { cs="reading a failure" } When an `assert` fails, pytest rewrites it behind the scenes to show the actual values it compared, not just that the statement was false — so a failure report reads like a diff, not a generic error. @@ -113,7 +116,7 @@ def test_species_count():
    -## Fixtures +## Fixtures { cs="fixtures" } A **fixture** is a function decorated with `@pytest.fixture` that builds some setup data once; any test function that names it as a parameter receives its return value automatically, without calling it directly. @@ -149,7 +152,7 @@ def test_snake_not_venomous(snake):
    -## Parametrizing tests +## Parametrizing tests { cs="parametrizing tests" } `@pytest.mark.parametrize` runs the same test function once per row of arguments, instead of copy-pasting a near-identical test for every case. @@ -187,9 +190,9 @@ def test_length_is_positive(species, length_ft):
    -## Testing for exceptions +## Testing for exceptions { cs="testing for exceptions" } -`pytest.raises()` is a context manager that asserts the code inside its `with` block raises a specific exception — a way to test the [`try`/`except`](../practices/errors.md#catch-with-tryexcept) paths in your own code, not just the successful ones. +`pytest.raises()` is a context manager that asserts the code inside its `with` block raises a specific exception — a way to test the [`try`/`except`](../../practices/errors.md#catch-with-tryexcept) paths in your own code, not just the successful ones. ```python-ref def test_invalid_length_raises(): diff --git a/docs/libraries/collections.md b/docs/libraries/utilities/collections.md similarity index 93% rename from docs/libraries/collections.md rename to docs/libraries/utilities/collections.md index 26b0185..610307d 100644 --- a/docs/libraries/collections.md +++ b/docs/libraries/utilities/collections.md @@ -1,4 +1,9 @@ --- +cheatsheet_title: collections +cheatsheet_description: 'Specialized containers: counting items, grouping with defaults, named tuples, fast queues.' +cheatsheet_title_suffix: :material-language-python:{ .library-badge .library-badge--builtin title="Built-in — included with Python" } +cheatsheet_attrs: + data-fcm-hide: essentials description: >- Specialized container types in Python's collections module: Counter, defaultdict, namedtuple, deque, OrderedDict, and ChainMap, with runnable examples. @@ -13,10 +18,10 @@ description: >- !!! note "Not the same as the Collections page" This page covers the **`collections` module** — extra container types imported with `from collections import ...`. For the built-in `list`, `dict`, `tuple`, and `set` types - themselves, see [Collections](../types/collections.md). + themselves, see [Collections](../../types/collections.md). The **`collections`** module adds specialized containers with added functionality on top of the -built-in [`str`](../types/basics.md#strings) [`list`](../types/collections.md#lists) [`dict`](../types/collections.md#dictionaries) [`tuple`](../types/collections.md#tuples) and [`set`](../types/collections.md#sets). +built-in [`str`](../../types/basics.md#strings) [`list`](../../types/collections.md#lists) [`dict`](../../types/collections.md#dictionaries) [`tuple`](../../types/collections.md#tuples) and [`set`](../../types/collections.md#sets).
    @@ -38,7 +43,7 @@ built-in [`str`](../types/basics.md#strings) [`list`](../types/collections.md#li
    -## Setup { data-card-link="skip" } +## Setup `collections` ships with Python's standard library — nothing to install. Each class is imported individually by name, rather than through a `collections.` prefix — so the import @@ -48,7 +53,7 @@ line differs per class, shown under its own "Import" heading below.
    -## Counter +## Counter { cs } `Counter` takes any iterable — a list, string, tuple, dict (its keys), set, or range — of hashable items (strings, numbers, booleans, other tuples) and returns a dict-like object @@ -60,15 +65,15 @@ counts = Counter(species) print(counts) # Counter({'ball': 3, 'burmese': 2, 'boa': 1}) ``` -### Import { data-card-link="skip" } +### Import ```python-ref from collections import Counter ``` -### Counter operations { data-card-link="skip" } +### Counter operations -#### Count +#### Count { cs="counts[item], most_common, total" } - **`counts[item]`** looks up an item's count. Missing items return `0` instead of raising `KeyError`, unlike indexing a plain dict. @@ -92,7 +97,7 @@ from collections import Counter counts.total() # 6 ``` -#### Inspect +#### Inspect { cs="elements" } - **`elements()`** does the reverse of counting — expands a `Counter` back out into an iterator that repeats each item by its count. @@ -101,7 +106,7 @@ from collections import Counter list(counts.elements()) # ['ball', 'ball', 'ball', 'burmese', 'burmese', 'boa'] ``` -#### Update +#### Update { cs="subtract, update" } - **`update()`** adds more items to an existing `Counter`, incrementing counts instead of replacing them — the counting equivalent of a list's `.extend()`. Passing another `Counter` @@ -118,7 +123,7 @@ from collections import Counter counts.subtract({"ball": 1, "cobra": 1}) # ball: 3, cobra: -1 ``` -#### Combine +#### Combine { cs="+ - & |" } - **`+` / `-` / `&` / `|`** combine two `Counter` objects item by item, returning a new one: add counts, subtract counts (dropping anything that would go negative), take the minimum of @@ -196,7 +201,7 @@ from collections import Counter
    -## defaultdict +## defaultdict { cs="defaultdict, default_factory" } A plain `dict` raises `KeyError` when indexing a missing key. `defaultdict` instead takes a **`default_factory`** — a type like `list`/`int`/`set`, or any other zero-argument callable @@ -219,13 +224,13 @@ print(by_venomous) # defaultdict(, {False: ['ball'], True: ['cobra'], 'unknown': ['burmese']}) ``` -### Import { data-card-link="skip" } +### Import ```python-ref from collections import defaultdict ``` -### Reading vs. writing +### Reading vs. writing { cs="get" } - **`snake.get("venomous", "unknown")`** only *reads* — it falls back to `"unknown"` for the burmese python's missing key instead of raising `KeyError`, the way `snake["venomous"]` @@ -317,7 +322,7 @@ from collections import defaultdict
    -## namedtuple +## namedtuple { cs } Builds a tuple subclass whose fields can be accessed by name (`snake.species`) as well as by position (`snake[0]`) — a lightweight alternative to a full class when all it needs to @@ -334,15 +339,15 @@ print(snake[0]) # "ball" — still works by position too Like a plain tuple, a `namedtuple` instance is immutable — there's no `snake.length_ft = 6`. -### Import { data-card-link="skip" } +### Import ```python-ref from collections import namedtuple ``` -### namedtuple operations { data-card-link="skip" } +### namedtuple operations -#### Create +#### Create { cs="_make, defaults=" } - **`namedtuple(name, fields)`** — `fields` can be a list of strings, or one space/comma-separated string (`"species length_ft"`). @@ -368,7 +373,7 @@ from collections import namedtuple Snake._make(row) # Snake(species='burmese', length_ft=12, venomous=False) ``` -#### Convert +#### Convert { cs="_asdict, _replace" } - **`_asdict()`** converts an instance to a regular dict. @@ -383,7 +388,7 @@ from collections import namedtuple snake._replace(length_ft=6) # Snake(species='ball', length_ft=6, venomous=False) ``` -#### Inspect +#### Inspect { cs="_field_defaults, _fields" } - **`_fields`** lists the field names; **`_field_defaults`** reports the defaults as a dict, the same information `defaults=` set, mapped back to field names. @@ -457,7 +462,7 @@ from collections import namedtuple
    -## deque +## deque { cs } Pronounced "deck" — short for "double-ended queue." A `deque` works like a list, but adding or removing items from the front (`appendleft()`, `popleft()`) is fast, where doing the same on a plain list @@ -473,15 +478,15 @@ queue.appendleft("blood") # add to the left end print(queue) # deque(['blood', 'ball', 'burmese', 'boa', 'cobra']) ``` -### Import { data-card-link="skip" } +### Import ```python-ref from collections import deque ``` -### deque operations { data-card-link="skip" } +### deque operations -#### Add +#### Add { cs="append, appendleft, extend, extendleft, insert" } - **`append()` / `appendleft()`** add one item to the right or left end. @@ -504,7 +509,7 @@ from collections import deque queue.insert(1, "viper") ``` -#### Remove +#### Remove { cs="clear, pop, popleft, remove" } - **`pop()` / `popleft()`** remove and return the item from the right or left end. @@ -521,7 +526,7 @@ from collections import deque queue.clear() ``` -#### Inspect +#### Inspect { cs="copy, count, index" } - **`count()` / `index()`** count occurrences of a value, or find its first position — same as on a list. @@ -537,7 +542,7 @@ from collections import deque backup = queue.copy() ``` -#### Reorder +#### Reorder { cs="maxlen=, reverse, rotate" } - **`rotate(n)`** shifts every item `n` places to the right (or left, with a negative `n`), wrapping the ones that fall off the end back around to the other side. @@ -637,7 +642,7 @@ from collections import deque
    -## OrderedDict +## OrderedDict { cs } Until Python 3.7 (released in 2018), a plain `dict` didn't guarantee it would remember insertion order — `OrderedDict` existed specifically to add that guarantee. Now it's mostly seen in legacy code written before 3.7, and in code that specifically needs its reordering functionality. @@ -645,15 +650,15 @@ Until Python 3.7 (released in 2018), a plain `dict` didn't guarantee it would re snake = OrderedDict([("species", "ball"), ("length_ft", 5), ("venomous", False)]) ``` -### Import { data-card-link="skip" } +### Import ```python-ref from collections import OrderedDict ``` -### OrderedDict operations { data-card-link="skip" } +### OrderedDict operations -#### Reorder +#### Reorder { cs="move_to_end, popitem" } - **`move_to_end(key, last=True)`** relocates an existing key to the back (or, with `last=False`, to the front). @@ -670,7 +675,7 @@ from collections import OrderedDict snake.popitem(last=False) # ('species', 'ball') ``` -#### Compare +#### Compare { cs="==" } - **`==`** checks order as well as contents — two plain dicts with the same items in a different order are still equal, but two `OrderedDict` objects aren't. @@ -683,7 +688,7 @@ from collections import OrderedDict
    -## ChainMap +## ChainMap { cs } Searches several dicts as if they were one, without copying or merging their contents. Looking up a key checks each dict in order and returns the first match — useful for layering @@ -703,15 +708,15 @@ print(snake["docile"]) # True — not in overrides, falls back to defaults Writing to a `ChainMap` (`snake["docile"] = False`) only ever changes the first dict in the chain — the rest are left untouched, read-only from the `ChainMap`'s point of view. -### Import { data-card-link="skip" } +### Import ```python-ref from collections import ChainMap ``` -### ChainMap operations { data-card-link="skip" } +### ChainMap operations -#### Extend +#### Extend { cs="new_child" } - **`new_child(m)`** returns a new `ChainMap` with `m` (an empty dict by default) added to the front — useful for pushing a fresh, temporary layer of overrides on top without @@ -722,7 +727,7 @@ from collections import ChainMap scoped["venomous"] # None — the new front dict wins ``` -#### Inspect +#### Inspect { cs="maps, parents" } - **`.maps`** is the underlying list of dicts, in search order, so it can be inspected or edited directly. @@ -742,7 +747,7 @@ from collections import ChainMap
    -## User\* wrapper classes +## User\* wrapper classes { cs="User* wrapper, UserDict, UserList, UserString" } `UserDict`, `UserList`, and `UserString` wrap a plain `dict`, `list`, or `str` for subclassing[^subclassing]. Subclassing `dict`/`list`/`str` directly is possible, but several of their @@ -760,7 +765,7 @@ snake["SPECIES"] = "ball" print(snake) # {'species': 'ball'} — key was lowercased on the way in ``` -### Import { data-card-link="skip" } +### Import ```python-ref from collections import UserDict, UserList, UserString diff --git a/docs/libraries/datetime.md b/docs/libraries/utilities/datetime.md similarity index 90% rename from docs/libraries/datetime.md rename to docs/libraries/utilities/datetime.md index 93e7fb0..7cf49de 100644 --- a/docs/libraries/datetime.md +++ b/docs/libraries/utilities/datetime.md @@ -1,4 +1,7 @@ --- +cheatsheet_title: datetime +cheatsheet_description: Calculating and formatting dates and times. +cheatsheet_title_suffix: :material-language-python:{ .library-badge .library-badge--builtin title="Built-in — included with Python" } description: >- Working with dates and times in Python using the datetime module: creating dates, date arithmetic, and formatting with strftime/strptime. @@ -23,7 +26,7 @@ The **`datetime`** module is Python's standard library for working with dates an
    -## Setup { data-card-link="skip" } +## Setup `datetime` ships with Python's standard library — nothing to install. Each class below is imported individually by name, rather than through a `datetime.` prefix. @@ -42,7 +45,7 @@ from datetime import date, datetime, timedelta
    -## Creating dates and times +## Creating dates and times { cs="date" } `date.today()` and `datetime.now()` read the current date (and time) directly from the system clock, so the value changes every time the code runs. @@ -56,7 +59,7 @@ print(today) print(now) ``` -### Creating a specific date +### Creating a specific date { cs="creating a specific date" } Pass the year, month, and day as plain integers to build a specific `date`. Useful for logging when a past observation actually happened, instead of reading today's date off the system clock. @@ -64,7 +67,7 @@ Pass the year, month, and day as plain integers to build a specific `date`. Usef observed = date(2026, 7, 23) # 2026-07-23 ``` -### Formatting with strftime +### Formatting with strftime { cs="strftime" } Turns a `date` or `datetime` into a custom-formatted string. `strftime` ("string format time") — `%B` is the full month name, `%d` the zero-padded day, `%Y` the four-digit year. It's the standard way to control exactly how a date is displayed. @@ -102,7 +105,7 @@ observed.strftime("%B %d, %Y") # "July 23, 2026"
    -## Date arithmetic +## Date arithmetic { cs="timedelta" } A `timedelta` represents a span of time, and adding one to a `date` or `datetime` shifts it forward (or backward, with a negative value) — the standard way to compute "a week from now" or "30 days ago." @@ -115,7 +118,7 @@ next_checkup = observed + timedelta(days=14) print(next_checkup) ``` -### Difference between two dates +### Difference between two dates { cs="difference between two dates" } Subtracting one `date` from another gives back a `timedelta`. Its `.days` attribute is the number of days between them — handy for measuring how long something has been tracked. @@ -125,7 +128,7 @@ last_seen = date(2026, 8, 6) last_seen - first_seen # timedelta(days=14) ``` -### Parsing a string with strptime +### Parsing a string with strptime { cs="strptime" } The reverse of `strftime` — reads a date out of a string. `strptime` ("string parse time") takes the same format codes describing how that string is laid out. This is how a date typed by a user, or read from a CSV file, gets turned back into a real `datetime` you can do arithmetic on. diff --git a/docs/libraries/math.md b/docs/libraries/utilities/math.md similarity index 93% rename from docs/libraries/math.md rename to docs/libraries/utilities/math.md index af09f87..916e585 100644 --- a/docs/libraries/math.md +++ b/docs/libraries/utilities/math.md @@ -1,4 +1,7 @@ --- +cheatsheet_title: math +cheatsheet_description: Rounding, roots, constants, and logarithms. +cheatsheet_title_suffix: :material-language-python:{ .library-badge .library-badge--builtin title="Built-in — included with Python" } description: >- Rounding, roots, constants, and logarithms in Python with the math module, with runnable examples. @@ -16,7 +19,7 @@ The **`math`** module extends Python's built-in arithmetic with functions it doe
    -## Setup { data-card-link="skip" } +## Setup `math` ships with Python's standard library — nothing to install. The whole module is used through the `math.` prefix, so a plain import is all you need. @@ -38,7 +41,7 @@ import math
    -## Rounding +## Rounding { cs="floor, ceil" } `floor()` and `ceil()` round down and up to the nearest integer. Unlike the built-in `round()`, they never round to the nearest value — `floor()` always goes down, `ceil()` always goes up. @@ -53,7 +56,7 @@ print(math.floor(avg)) print(math.ceil(avg)) ``` -### trunc +### trunc { cs } Chops off the decimal part instead of rounding toward a direction — the same as `floor()` for a positive number, but different for a negative one, where it rounds toward zero instead of down. @@ -85,7 +88,7 @@ math.trunc(-6.75) # -6 — floor(-6.75) would be -7
    -## Roots and powers +## Roots and powers { cs="sqrt" } `sqrt()` finds a square root — useful anywhere the Pythagorean theorem shows up, like the diagonal brace of a square enclosure. @@ -98,7 +101,7 @@ diagonal = math.sqrt(side_ft ** 2 + side_ft ** 2) print(diagonal) ``` -### pow +### pow { cs="pow, isqrt" } Raises a number to a power, same idea as the `**` operator — but `math.pow()` always returns a `float`, even when the inputs are whole numbers, while `**` keeps an integer result an `int`. @@ -133,7 +136,7 @@ math.pow(side_ft, 2) # 16.0 — always a float
    -## Constants +## Constants { cs="pi, inf, nan" } `math.pi` is the constant π, accurate to the precision of a `float` — no need to type out `3.14159...` by hand. @@ -185,7 +188,7 @@ print(circumference)
    -## Logarithms +## Logarithms { cs="log2, log10, exp" } `log2()` is the inverse of doubling — how many times a starting value has to double to reach a target. A breeding program tracking how many generations it takes to go from 2 snakes to 64 is a direct fit. @@ -227,7 +230,7 @@ math.exp(1) # 2.718281828459045 — the same as math.e
    -## Comparing floats +## Comparing floats { cs="isclose" } Floating-point math loses tiny amounts of precision, so two values that should be mathematically equal often aren't exactly equal in code. `math.isclose()` checks whether two numbers are close enough to count as equal instead of comparing them bit for bit. diff --git a/docs/libraries/random.md b/docs/libraries/utilities/random.md similarity index 89% rename from docs/libraries/random.md rename to docs/libraries/utilities/random.md index 380734b..44e2cff 100644 --- a/docs/libraries/random.md +++ b/docs/libraries/utilities/random.md @@ -1,4 +1,7 @@ --- +cheatsheet_title: random +cheatsheet_description: Random numbers, random picks, shuffled order. +cheatsheet_title_suffix: :material-language-python:{ .library-badge .library-badge--builtin title="Built-in — included with Python" } description: Generating random numbers and making random selections in Python with the random module, with runnable examples. --- @@ -14,7 +17,7 @@ The **`random`** module generates pseudo-random numbers and makes random selecti
    -## Setup { data-card-link="skip" } +## Setup `random` ships with Python's standard library — nothing to install. The whole module is used through the `random.` prefix, so a plain import is all you need. @@ -34,7 +37,7 @@ import random
    -## Random numbers +## Random numbers { cs="randint" } `random()` and `randint()` are the two basic building blocks — a random fraction, or a random whole number within a range. @@ -73,7 +76,7 @@ print(random.randint(1, 6))
    -## Random selections +## Random selections { cs="choice" } `choice()` picks one item at random from an existing sequence — no need to generate a number and index into the list by hand. @@ -85,7 +88,7 @@ species = ["ball", "burmese", "boa", "blood"] print(random.choice(species)) ``` -### Shuffling a list +### Shuffling a list { cs="shuffle" } Reorders a list randomly, in place. `shuffle()` returns `None`, so the point is the side effect on `species` itself, not a return value to assign. @@ -95,7 +98,7 @@ random.shuffle(species) species # e.g. ["boa", "ball", "blood", "burmese"] — order is randomized ``` -### Sampling without replacement +### Sampling without replacement { cs="sample" } Picks several items at once, all guaranteed distinct. Unlike calling `choice()` in a loop, which could return the same item twice. The original list is left unchanged; `sample()` returns a new list. diff --git a/docs/libraries/re.md b/docs/libraries/utilities/re.md similarity index 91% rename from docs/libraries/re.md rename to docs/libraries/utilities/re.md index 4521dce..35b8bc4 100644 --- a/docs/libraries/re.md +++ b/docs/libraries/utilities/re.md @@ -1,4 +1,8 @@ --- +cheatsheet_title: re +cheatsheet_description: 'Regular expressions: searching, extracting, and replacing text by pattern.' +cheatsheet_icon: material-text-search +cheatsheet_title_suffix: :material-language-python:{ .library-badge .library-badge--builtin title="Built-in — included with Python" } description: >- Regular expressions in Python with the re module: searching, extracting groups, replacing, and splitting text, with runnable examples. @@ -16,7 +20,7 @@ The **`re`** module works with regular expressions — patterns that describe te
    -## Setup { data-card-link="skip" } +## Setup `re` ships with Python's standard library — nothing to install. The whole module is used through the `re.` prefix, so a plain import is all you need. Patterns are written as **raw strings** (`r"..."`), so a backslash like `\d` is passed straight to `re` instead of Python trying to interpret it as a string escape sequence first. @@ -45,7 +49,7 @@ import re
    -## Searching for a pattern +## Searching for a pattern { cs="search, compile" } `re.search()` scans the text and returns a `Match` object for the first hit, or `None` if the pattern never occurs. `.group()` reads the actual matched text back out of it. @@ -74,7 +78,7 @@ print(match.group()) Calling `re.search()` (or `.findall()`, `.sub()`, etc.) with a raw pattern string repeats the same compilation work internally on every call, even when the pattern never changes — across a loop of n calls, that's n compilations of the same pattern. Compiling it once with `re.compile()` above the loop and calling `.search()` on the result instead does that work exactly once, however many times the loop runs. - See [Efficiency](../practices/style.md#efficiency) for why this distinction matters. + See [Efficiency](../../practices/style.md#efficiency) for why this distinction matters. ??? run "Run a searching example" All the examples above, combined into one script: @@ -99,7 +103,7 @@ print(match.group())
    -## Finding all matches +## Finding all matches { cs="findall" } `re.findall()` returns every match in the text as a list, instead of stopping at the first one. @@ -111,7 +115,7 @@ notes = "ball: 4ft, burmese: 12ft, boa: 8ft" print(re.findall(r"\d+ft", notes)) ``` -### Groups +### Groups { cs="groups, named groups" } Parentheses in a pattern mark a **capturing group** — a piece of the match to pull out on its own. With groups in the pattern, `findall()` returns a list of tuples, one tuple of group values per match, instead of a list of whole matches. @@ -156,7 +160,7 @@ re.findall(r"(\w+): (\d+)ft", notes) # [("ball", "4"), ("burmese", "12"), ("b
    -## Replacing text +## Replacing text { cs="sub" } `re.sub()` replaces every match with a new string. `\1` in the replacement refers back to the first capturing group in the pattern, so part of each match can be kept while the rest changes. @@ -183,7 +187,7 @@ print(re.sub(r"(\d+)ft", r"\1 feet", notes))
    -## Splitting on a pattern +## Splitting on a pattern { cs="split" } `re.split()` breaks text apart wherever the pattern matches, similar to `str.split()` but able to split on more than one exact separator at once. diff --git a/docs/libraries/time.md b/docs/libraries/utilities/time.md similarity index 91% rename from docs/libraries/time.md rename to docs/libraries/utilities/time.md index 825a68b..9654330 100644 --- a/docs/libraries/time.md +++ b/docs/libraries/utilities/time.md @@ -1,4 +1,7 @@ --- +cheatsheet_title: time +cheatsheet_description: Reading the system clock, pausing execution, and measuring elapsed time. +cheatsheet_title_suffix: :material-language-python:{ .library-badge .library-badge--builtin title="Built-in — included with Python" } description: >- Reading the system clock, pausing execution, and measuring elapsed time in Python with the time module. @@ -23,7 +26,7 @@ The **`time`** module reads the system clock, pauses a program for a set number
    -## Setup { data-card-link="skip" } +## Setup `time` ships with Python's standard library — nothing to install. The whole module is used through the `time.` prefix, so a plain import is all you need. @@ -43,7 +46,7 @@ import time
    -## Reading the clock +## Reading the clock { cs="time" } `time()` returns the number of seconds since the epoch[^epoch] — a single float that always increases, useful for a timestamp or for logging when an observation happened. @@ -59,7 +62,7 @@ print(time.time())
    -## Pausing execution +## Pausing execution { cs="sleep" } `sleep()` pauses the program for the given number of seconds before continuing to the next line. Useful for spacing out repeated `print()` calls, or waiting between requests to an external service. @@ -90,7 +93,7 @@ print("still there.")
    -## Measuring elapsed time +## Measuring elapsed time { cs="perf_counter" } `perf_counter()` reads a high-resolution timer meant for measuring durations, not for reading the wall-clock date — call it before and after a block of code, then subtract the two readings to get the elapsed time in seconds. @@ -124,7 +127,7 @@ print(elapsed)
    -## Formatting the current time +## Formatting the current time { cs="localtime, strftime" } `localtime()` returns a `struct_time` — the current date and time broken into named fields (`tm_year`, `tm_hour`, `tm_min`, and so on). `strftime()` turns one into a custom-formatted string, using the same format codes as [`datetime`'s `strftime`](datetime.md#formatting-with-strftime): `%H` the zero-padded hour, `%M` the zero-padded minute. diff --git a/docs/libraries/beautifulsoup.md b/docs/libraries/web/beautifulsoup.md similarity index 85% rename from docs/libraries/beautifulsoup.md rename to docs/libraries/web/beautifulsoup.md index 17b92b4..b8df3d2 100644 --- a/docs/libraries/beautifulsoup.md +++ b/docs/libraries/web/beautifulsoup.md @@ -1,4 +1,7 @@ --- +cheatsheet_title: BeautifulSoup +cheatsheet_description: 'Parsing HTML: finding tags, reading attributes and text, and turning a page into structured data.' +cheatsheet_title_suffix: :material-download-outline:{ .library-badge .library-badge--third-party title="Third-party — install separately with pip" } description: >- Parsing HTML in Python with BeautifulSoup: finding tags, reading attributes and text, and turning a page into structured data for web scraping. @@ -18,7 +21,7 @@ BeautifulSoup is an open-source project maintained by volunteer contributors.
    -## Setup { data-card-link="skip" } +## Setup ```bash pip install beautifulsoup4 @@ -34,7 +37,7 @@ from bs4 import BeautifulSoup
    -## HTML and web pages +## HTML and web pages { cs } A web page's content is just text — a file written in **HTML** (HyperText Markup Language), where tags mark what each piece of text is: a heading, a paragraph, a link, an image. Tags nest inside each other to build up a whole page's structure, the same way a list can hold another list. A browser doesn't show you this markup directly — it reads the HTML and *renders* it, turning `

    Ball python

    ` into large, bold text on screen instead of displaying the angle brackets themselves. @@ -47,7 +50,7 @@ A web page's content is just text — a file written in **HTML** (HyperText Mark ``` -A website doesn't send a picture of its page — it sends this raw HTML text, the same way [requests](requests.md) fetches a JSON API's response. Every browser's "View Page Source" (or a right-click "Inspect") shows exactly this text for any page you're looking at, which is worth trying on a real site before scraping one — it's the same markup a scraper reads. +A website doesn't send a picture of its page — it sends this raw HTML text, the same way [requests](../apis/requests.md) fetches a JSON API's response. Every browser's "View Page Source" (or a right-click "Inspect") shows exactly this text for any page you're looking at, which is worth trying on a real site before scraping one — it's the same markup a scraper reads. Web scraping is just skipping the rendering step. `requests.get(url).text` returns this same raw HTML a browser would've turned into a page, as a plain Python string. BeautifulSoup is what makes that string usable — a `BeautifulSoup` object rebuilds the tag structure a browser's rendering engine reads, so a program can search it by tag and attribute exactly as it appears in the markup, without drawing anything on screen. @@ -55,9 +58,9 @@ Web scraping is just skipping the rendering step. `requests.get(url).text` retur
    -## Overview { data-card-link="skip" } +## Overview -It only works with HTML that's already in hand — a file, a plain string, or the `.text` of a [requests](requests.md) response — it has no ability to fetch a page itself, which is why the two are almost always used together: `requests` gets the page, BeautifulSoup makes sense of it. Parsing needs no network access, so unlike `requests`, most examples on this page run directly in this site's browser sandbox; the last one, which fetches a real page, does not. +It only works with HTML that's already in hand — a file, a plain string, or the `.text` of a [requests](../apis/requests.md) response — it has no ability to fetch a page itself, which is why the two are almost always used together: `requests` gets the page, BeautifulSoup makes sense of it. Parsing needs no network access, so unlike `requests`, most examples on this page run directly in this site's browser sandbox; the last one, which fetches a real page, does not. | Concept | What it is | |---------|------------| @@ -79,7 +82,7 @@ For a single page already in hand, BeautifulSoup offers the best balance of simp
    -## Parsing HTML +## Parsing HTML { cs } `BeautifulSoup(html, "html.parser")` reads a string of HTML and returns a navigable object with the same tree structure as the page itself — call `.find()` on it, or walk straight to a tag as if it were an attribute, to reach any piece of it. @@ -109,7 +112,7 @@ soup = BeautifulSoup(html, "html.parser") print(soup.h2.text) ``` -### Finding tags +### Finding tags { cs } `.find()` returns the first matching tag; `.find_all()` returns every match, as a list. Both take a tag name, and narrow further with `class_=` (a trailing underscore, since `class` alone is a reserved word in Python) or `attrs={...}` for any other attribute. @@ -183,7 +186,7 @@ print(soup.find("div", attrs={"data-length-ft": "20"}).h2.text) print("not found") ``` -### Reading text and attributes +### Reading text and attributes { cs } `.text` (or `.get_text()`) returns everything inside a tag as one string, including any nested tags' text. An attribute reads like a dict item — `tag["href"]` — or safely with `.get("href")`, which returns `None` instead of raising `KeyError` when the attribute isn't there. `.attrs` gives every attribute on a tag as a plain dict. @@ -217,9 +220,9 @@ print(tag.attrs)
    -## Extracting structured data +## Extracting structured data { cs } -A page is rarely useful one tag at a time — the real value of `find_all()` is looping over its results to build a plain Python list, the same list-of-dicts shape as [Collections](../types/collections.md#dictionaries)' own snake catalog, ready to filter, sort, or save to a [CSV](csv.md) or [JSON](json.md) file. +A page is rarely useful one tag at a time — the real value of `find_all()` is looping over its results to build a plain Python list, the same list-of-dicts shape as [Collections](../../types/collections.md#dictionaries)' own snake catalog, ready to filter, sort, or save to a [CSV](../data_analysis/csv.md) or [JSON](../apis/json.md) file. ```python-ref snakes = [] @@ -267,13 +270,13 @@ for snake in snakes:
    -## Putting it together +## Putting it together { cs } -BeautifulSoup only parses HTML that's already in hand — pairing it with [requests](requests.md) is what turns this into an actual web scraper. A handful of small functions cover most everyday tasks; write each one once, then reuse it on any page. +BeautifulSoup only parses HTML that's already in hand — pairing it with [requests](../apis/requests.md) is what turns this into an actual web scraper. A handful of small functions cover most everyday tasks; write each one once, then reuse it on any page. -### Common tasks +### Common tasks { cs } -Each function below wraps a single `find`/`find_all` call in a [function](../organization/functions.md), returning a plain [list](../types/collections.md#lists) built with a [list comprehension](../types/collections.md#list-comprehension) — store the result in a variable and use it like any other value. +Each function below wraps a single `find`/`find_all` call in a [function](../../organization/functions.md), returning a plain [list](../../types/collections.md#lists) built with a [list comprehension](../../types/collections.md#list-comprehension) — store the result in a variable and use it like any other value. ```python-ref get_title(soup) # "Ball python" @@ -320,7 +323,7 @@ print(get_all_headings(soup)) print(get_all_paragraphs(soup)) ``` -### Scraping a real page +### Scraping a real page { cs } The same functions work unchanged on a real page — only the URL, and which tags you care about, change. `requests.get(url).text` fetches the page; `BeautifulSoup` parses it exactly as above. This makes a real network call, which this site's sandbox can't do — copy it into a local `.py` file, swap in any page's URL, and see what comes back. diff --git a/docs/llms.txt b/docs/llms.txt index ca07e91..6ea3aaf 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -22,17 +22,17 @@ Code blocks throughout the site run live in the browser via Pyodide, so examples ## Libraries - [Libraries overview](https://pythonfieldguide.com/libraries/): Built-in and third-party libraries covered on this site. -- [csv](https://pythonfieldguide.com/libraries/csv/): csv.writer, csv.reader, and DictReader. -- [datetime](https://pythonfieldguide.com/libraries/datetime/): Creating dates, date arithmetic, and formatting. -- [json](https://pythonfieldguide.com/libraries/json/): json.load, json.dump, and nested data. -- [random](https://pythonfieldguide.com/libraries/random/): Random numbers and random selections. -- [Tkinter](https://pythonfieldguide.com/libraries/tkinter/): Widgets, layout managers, event handling, and ttk styling. -- [matplotlib](https://pythonfieldguide.com/libraries/matplotlib/): Line plots, bar charts, scatter plots, subplots, and saving figures. -- [NumPy](https://pythonfieldguide.com/libraries/numpy/): Arrays, vectorized math, aggregation, and boolean-mask filtering. -- [OpenCV](https://pythonfieldguide.com/libraries/opencv/): Reading images, color spaces, edge detection, contours, face detection. -- [pandas](https://pythonfieldguide.com/libraries/pandas/): Building a DataFrame, sorting rows, and summarizing columns. -- [Pillow](https://pythonfieldguide.com/libraries/pillow/): Resizing, cropping, drawing, filters, and format conversion. -- [requests](https://pythonfieldguide.com/libraries/requests/): Fetching data, checking status codes, parsing JSON, and handling errors. +- [csv](https://pythonfieldguide.com/libraries/data_analysis/csv/): csv.writer, csv.reader, and DictReader. +- [datetime](https://pythonfieldguide.com/libraries/utilities/datetime/): Creating dates, date arithmetic, and formatting. +- [json](https://pythonfieldguide.com/libraries/apis/json/): json.load, json.dump, and nested data. +- [random](https://pythonfieldguide.com/libraries/utilities/random/): Random numbers and random selections. +- [Tkinter](https://pythonfieldguide.com/libraries/desktop_uis/tkinter/): Widgets, layout managers, event handling, and ttk styling. +- [matplotlib](https://pythonfieldguide.com/libraries/data_analysis/matplotlib/): Line plots, bar charts, scatter plots, subplots, and saving figures. +- [NumPy](https://pythonfieldguide.com/libraries/data_analysis/numpy/): Arrays, vectorized math, aggregation, and boolean-mask filtering. +- [OpenCV](https://pythonfieldguide.com/libraries/computer_vision/opencv/): Reading images, color spaces, edge detection, contours, face detection. +- [pandas](https://pythonfieldguide.com/libraries/data_analysis/pandas/): Building a DataFrame, sorting rows, and summarizing columns. +- [Pillow](https://pythonfieldguide.com/libraries/images/pillow/): Resizing, cropping, drawing, filters, and format conversion. +- [requests](https://pythonfieldguide.com/libraries/apis/requests/): Fetching data, checking status codes, parsing JSON, and handling errors. ## Optional diff --git a/docs/organization/classes.md b/docs/organization/classes.md index cc376e1..c8bfc22 100644 --- a/docs/organization/classes.md +++ b/docs/organization/classes.md @@ -1,4 +1,5 @@ --- +cheatsheet_description: Bundle related values and functions to a reusable blueprint for similar objects. description: >- Python classes and object-oriented programming explained with runnable examples: attributes, methods, property/staticmethod/classmethod, and inheritance. @@ -22,7 +23,7 @@ A **class** bundles related data together with the behavior (methods) that acts
    -## Defining a class +## Defining a class { cs="class" } A class is a blueprint for creating objects — it defines what attributes and methods every object built from it will have. An object is one specific instance built from that blueprint, with its own copy of the attributes. @@ -57,7 +58,7 @@ What `ball = Snake("ball", 5)` does: `burmese = Snake("burmese", 16)` builds a separate object the same way — `burmese.species` and `ball.species` don't share data, same as two function calls (previous page) don't share local variables. -### The `__init__()` method +### The `__init__()` method { cs="__init__()" } Runs automatically every time a new object is created — step 1 above. It's where an object's starting attributes get set up. Python calls this a **constructor**. You never call `__init__()` directly — `Snake("ball", 5)` is what triggers Python to call it. @@ -90,7 +91,7 @@ ball = Snake("ball", 5) # __init__ runs automatically, setting ball.species a self.tags = tags if tags is not None else [] # a new list every time ``` -### The self parameter +### The self parameter { cs="self" } Refers to the specific object a method was called on. One `Snake` class, but many `Snake` objects (`ball`, `burmese`, ...) sharing its method code — `self` is how a method written once still knows which object to act on. @@ -107,7 +108,7 @@ ball.describe() # self is ball → "a 5 ft ball python" burmese.describe() # self is burmese → "a 16 ft burmese python" ``` -### Object methods +### Object methods { cs="methods" } A method is a function defined inside a class — parameters, `return`, and defaults all work the same as on the [Functions](functions.md) page. The one addition is `self`, which lets it read or change that specific object's own attributes. @@ -115,7 +116,7 @@ A method is a function defined inside a class — parameters, `return`, and defa ball.describe() # "a 5 ft ball python" ``` -### Instance attributes +### Instance attributes { cs="instance attributes" } An instance attribute is set with `self.x = value`, usually inside `__init__`. This is the default way a class stores data — each object gets its own independent copy, separate from every other object's. @@ -159,7 +160,7 @@ For a value every object should share instead of holding its own copy, see [clas
    -### Class attributes +### Class attributes { cs="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. @@ -189,7 +190,7 @@ print(burmese.kingdom) # "Animalia" — unaffected | Changing it on one object | Only that object sees the change | Reassigning through the class changes it for every object that hasn't shadowed it | | Use it for | Data that's different for each object — `species`, `length_ft` | A value every object of the class shares — a constant, a shared default, a running count | -### Going further { data-card-link="skip" } +### Going further ??? tip "The `__str__()` method" Controls what `print()` shows for an object, instead of its memory address. By default, `print()`-ing an object just shows its memory address, which isn't very useful. @@ -320,7 +321,7 @@ print(burmese.kingdom) # "Animalia" — unaffected
    -## Method decorators { data-fcm-hide="essentials" } +## Method decorators { data-fcm-hide="essentials" cs="method decorators" } Python provides 3 built-in [decorators](functions.md#decorators) for methods that change how the method is called and add functionality: @@ -348,9 +349,9 @@ Snake.is_valid_length(5) # True — called on the class, no obje Snake.from_cm("ball", 152.4).length_ft # 5.0 — builds a new object instead of modifying one ``` -### @property +### @property { cs } -Call it like a plain attribute, no parentheses. Turns a method into a value computed fresh every time it's read, instead of stored and going stale — `length_cm` below always reflects the current `length_ft`, even if it changes later. +Call it like a plain attribute, no parentheses. Turns a method into a value computed fresh every time it's read, instead of stored and going stale — `length_cm` below always reflects the current `length_ft`, even if it changes later. Use it for a value that's cheap to derive from existing attributes and should look like a plain attribute to the rest of the code; skip it if the computation is expensive to redo on every access, or needs its own arguments beyond `self`. @@ -376,15 +377,15 @@ Use it for a value that's cheap to derive from existing attributes and should lo ball.length_ft # 10.0 ``` -### @staticmethod +### @staticmethod { cs } -Call it without needing an object at all, directly on the class. Removes the automatic `self`, so the method can't read or change any object's data — it's really just a plain function, grouped under the class because it's conceptually related. +Call it without needing an object at all, directly on the class. Removes the automatic `self`, so the method can't read or change any object's data — it's really just a plain function, grouped under the class because it's conceptually related. Use it for logic tied to the class's purpose but not to any one object's state, like a validation check; if it needs `self`, it should be a regular method instead. -### @classmethod +### @classmethod { cs } -Call it as an alternative way to build an object. Receives the class itself (conventionally named `cls`) instead of an object, so it can construct and return a new instance. +Call it as an alternative way to build an object. Receives the class itself (conventionally named `cls`) instead of an object, so it can construct and return a new instance. Use it when there's more than one sensible way to build an object — `Snake.from_cm(...)` alongside the usual `Snake(...)` — as a second, clearly-named constructor; skip it if there's only one way to build the object, since `__init__()` would be complete. @@ -392,7 +393,7 @@ Use it when there's more than one sensible way to build an object — `Snake.fro
    -## Inheritance +## Inheritance { cs="inheritance" } A child class reuses — and can extend or override — everything defined in a parent class, instead of rewriting it from scratch. The parent is also called the **base class**; the child is the **derived class**. @@ -413,7 +414,7 @@ boa = Boa("boa constrictor", 10) print(boa.describe()) ``` -### Overriding `__init__()` +### Overriding `__init__()` { cs="__init__()" } Adding `__init__()` to a child class replaces the parent's version entirely. Call `Parent.__init__(self, ...)` explicitly inside it if you still want the parent's setup to run too. @@ -424,7 +425,7 @@ class Boa(Snake): self.region = region ``` -### Using super() +### Using super() { cs="super()" } Calls the parent's version of a method without naming the parent class directly. The usual, cleaner way to do what the previous example did by hand. @@ -432,7 +433,7 @@ Calls the parent's version of a method without naming the parent class directly. super().__init__(species, length_ft) # same as Snake.__init__(self, species, length_ft), without naming the parent ``` -### Adding attributes and methods +### Adding attributes and methods { cs="adding attributes and methods" } A child class isn't limited to what its parent has. It can define brand-new attributes and methods of its own, on top of everything it inherits. @@ -441,7 +442,7 @@ boa.region # "south america" — new attribute, parent Snake has no such t boa.habitat() # new method, only Boa has it ``` -### Overriding methods +### Overriding methods { cs="overriding" } Defining a method in the child class with the exact same name as one in the parent replaces the parent's version for that child. This is the foundation of polymorphism, covered next. @@ -450,7 +451,7 @@ snake.describe() # "a 5 ft ball python" — Snake's own version boa.describe() # "a heavy-bodied constrictor" — Boa's version replaces it ``` -### Multiple inheritance { data-fcm-hide="essentials" } +### Multiple inheritance { data-fcm-hide="essentials" cs="multiple inheritance" } A class can list more than one parent, comma-separated — it inherits the combined attributes and methods of all of them. When two parents define the same method, Python searches left to right through the parents listed and uses the first match — this search order is called the **MRO** (method resolution order). @@ -472,7 +473,7 @@ print(cobra.warning()) # "handle with extreme caution" — Venomous is listed `Cobra.__mro__` shows the actual search order Python used, in case more than two parents makes it unclear. -### Going further { data-card-link="skip" } +### Going further ??? run "Run an inheritance example" All the examples above, combined into one script: @@ -569,7 +570,7 @@ print(cobra.warning()) # "handle with extreme caution" — Venomous is listed
    -## Polymorphism { data-fcm-hide="essentials" } +## Polymorphism { data-fcm-hide="essentials" cs="polymorphism" } **Polymorphism** ("many forms") means the same method or function name behaves differently depending on which object it's called on — so you can call `.describe()` on any snake-like object without needing to know exactly which one it is. @@ -579,7 +580,7 @@ print(len(["ball", "burmese", "boa"])) print(len({"species": "ball", "length_ft": 5})) ``` -### Duplicate method names +### Duplicate method names { cs="duplicate method names" } Classes don't need to be related by inheritance to share a method name. As long as each one defines its own `.move()`, calling it works the same way no matter which object it's called on. @@ -588,7 +589,7 @@ ball.move() # "slither" gecko.move() # "climb" ``` -### Polymorphism via inheritance +### Polymorphism via inheritance { cs="inheritance" } Looping over a mix of parent and child objects and calling the same method name runs each object's own version automatically. This is the more common case — a child class overrides a parent's method, as in the previous section. @@ -598,7 +599,7 @@ for s in (snake, boa): print(s.describe()) # a heavy-bodied constrictor ``` -### Going further { data-card-link="skip" } +### Going further ??? run "Run a polymorphism example" All the examples above, combined into one script: @@ -649,11 +650,11 @@ for s in (snake, boa): print(s.describe())
    -## Encapsulation { data-fcm-hide="essentials" } +## Encapsulation { data-fcm-hide="essentials" cs="encapsulation" } **Encapsulation** restricts direct access to an object's data, so it can only be read or changed through the class's own methods. Python doesn't enforce this the way some other languages do — it's a naming convention the caller is trusted to respect, not a hard restriction. -### Single underscore +### Single underscore { cs="single underscore" } A leading underscore (`_species`) signals "internal — not part of the class's public interface." Python doesn't actually stop outside code from reading or changing it; it's a convention, not a lock. @@ -666,7 +667,7 @@ ball = Snake("ball", 5) ball._species # "ball" — still accessible, just a signal not to ``` -### Double underscore +### Double underscore { cs="double underscore" } A leading double underscore (`__species`) triggers **name mangling** — Python renames the attribute internally to `_ClassName__species`, making it awkward (though still not impossible) to reach from outside the class. @@ -680,7 +681,7 @@ ball.__species # AttributeError — not found under this name ball._Snake__species # "ball" — the actual mangled name ``` -### Controlled access with @property +### Controlled access with @property { cs="@property" } Pair an underscore-prefixed attribute with [`@property`](#property) to actually enforce something — like validation — instead of only signaling intent. @@ -707,7 +708,7 @@ ball.length_ft = -1 # ValueError — blocked by the setter
    -## Operator overloading { data-fcm-hide="essentials" } +## Operator overloading { data-fcm-hide="essentials" cs="operator overloading" } Defining a dunder method lets a built-in operator (`==`, `<`, `+`, ...) work on your own objects — the same mechanism as [`__str__()`](#defining-a-class) and [`__repr__()`](#defining-a-class), just for operators instead of printing. @@ -716,7 +717,7 @@ ball = Snake("ball", 5) ball == Snake("ball", 5) # False — without __eq__, Python compares by identity, not by data ``` -### Comparing with `__eq__` and `__lt__` +### Comparing with `__eq__` and `__lt__` { cs="__eq__ and __lt__" } `__eq__` defines what `==` does; `__lt__` defines what `<` does. Without them, `==` falls back to comparing identity (is this the exact same object?) rather than the data inside. @@ -739,7 +740,7 @@ print(ball == Snake("ball", 5)) # True — same length_ft print(ball < burmese) # True — 5 < 16 ``` -### Arithmetic with `__add__` +### Arithmetic with `__add__` { cs="__add__" } `__add__` defines what `+` does between two objects — whatever combining them should mean for this class. @@ -762,7 +763,7 @@ print(ball + burmese) # 21 — combined length
    -## Dataclasses { data-fcm-hide="essentials" } +## Dataclasses { data-fcm-hide="essentials" cs="dataclasses" } `@dataclass` generates `__init__()` and `__repr__()` automatically from a list of typed attributes, instead of writing them by hand. @@ -796,7 +797,7 @@ Use it for a class that's mostly just holding data, with little or no custom beh
    -## Abstract base classes { data-fcm-hide="essentials" } +## Abstract base classes { data-fcm-hide="essentials" cs="abstract base classes" } An **abstract base class** defines methods that every subclass must implement, using `abc.ABC` and `@abstractmethod`. Trying to create an object from a class that hasn't implemented all of them raises a `TypeError` immediately, instead of failing later when the missing method actually gets called. diff --git a/docs/organization/functions.md b/docs/organization/functions.md index b169bc7..d9f325a 100644 --- a/docs/organization/functions.md +++ b/docs/organization/functions.md @@ -1,4 +1,5 @@ --- +cheatsheet_description: Package a named block of code to run it at any time. description: >- Python functions explained with runnable examples: defining, calling, arguments, *args/**kwargs, scope, recursion, and decorators. @@ -8,7 +9,7 @@ description: >-
    -A **function** packages a block of code under a name, so it can be run again — with different inputs — instead of copying and pasting the same lines every time you need them. +A **function** packages a block of code under a name, so it can be run again — with different inputs — instead of copying and pasting the same lines every time you need them. Python already has some built in (`print()`, `len()`, `input()`), but `def` lets you write your own. @@ -101,11 +102,11 @@ message = describe("ball") # "a ball python" is the return value, so now
    -## Defining a function +## Defining a function { cs="def" } -A function is first defined. After it's defined, you can [call the function](#calling-a-function) whenever you need to run it. +A function is first defined. After it's defined, you can [call the function](#calling-a-function) whenever you need to run it. -The function definition line contains **`def`**, a **function name** (follows the same [naming rules as variables](../start/foundations.md#naming-variables)), **parentheses** holding zero or more **parameters**, and a **colon**. +The function definition line contains **`def`**, a **function name** (follows the same [naming rules as variables](../start/foundations.md#naming-variables)), **parentheses** holding zero or more **parameters**, and a **colon**. Under it is an indented **body**: the block of code that runs when the function is [called](#calling-a-function). @@ -123,7 +124,7 @@ def function_name(optional_parameter, optional_parameter): | Indent selected lines | ++tab++ | | Unindent selected lines | ++shift+tab++ | -### Parameters +### Parameters { cs="parameters" } A **parameter** is the placeholder name listed in a function's own definition — as opposed to an **argument**, the actual value a caller passes in for it. @@ -139,12 +140,12 @@ describe("ball", 5) # a 5 ft ball python describe(5, "ball") # a ball ft 5 python — wrong order, but still runs ``` -#### Default values +#### Default values { cs="defaults" } A parameter can fall back to a default value if the call doesn't specify one. Parameters with a default must come **after** all of the parameters without one. ```python-ref -def describe(species, length_ft=5): # default length_ft is 5, if not given then called. +def describe(species, length_ft=5): # default length_ft is 5, if not given then called. return f"a {length_ft} ft {species} python" describe("burmese", 12) # length_ft is 12 @@ -173,7 +174,7 @@ describe("ball") # second parameter is not given, so length_ft is the d return log ``` -#### *args tuple +#### *args tuple { cs="*args" } `*args` collects any number of positional arguments into a single [tuple](../types/collections.md#tuples), so a function can accept as many as the caller passes instead of a fixed list of parameters. `*args` is the conventional name, but any name after `*` works. @@ -184,7 +185,7 @@ def total_length(*args): total_length(5, 12, 8) # 25 ``` -#### **kwargs dict +#### **kwargs dict { cs="**kwargs" } `**kwargs` collects any number of keyword arguments into a single [dict](../types/collections.md#dictionaries), so a function can accept as many `name=value` pairs as the caller passes instead of a fixed list of parameters. `**kwargs` is the conventional name, but any name after `**` works. @@ -195,7 +196,7 @@ def describe(**details): describe(species="ball", length_ft=5) ``` -#### Type hints { data-fcm-hide="essentials" } +#### Type hints { data-fcm-hide="essentials" cs="type hints" } A type hint on a parameter like `species: str` annotates the type of value it's expected to receive. Python doesn't enforce it, but it can be helpful for you to keep track of it and a separate type checker (like `mypy`) can check for you. @@ -204,14 +205,14 @@ def describe(species: str, length_ft: float): return f"a {length_ft} ft {species} python" ``` -#### Combining categories { data-fcm-hide="essentials" , data-card-link="skip" } +#### Combining categories { data-fcm-hide="essentials" cs="combining argument types" } -A single signature can mix kinds of parameters, but must be in this order: +A single signature can mix kinds of parameters, but must be in this order: 1. positional parameters 2. `*args` 3. keyword-only parameters -4. `**kwargs` +4. `**kwargs` `venomous` sits after `*lengths`, which makes it keyword-only automatically — anything named after `*args` can only be passed by name, even without a separate bare `*`. @@ -223,7 +224,7 @@ describe("ball", 5, 6, venomous=True, habitat="captive") # species = "ball", lengths = (5, 6), venomous = True, details = {"habitat": "captive"} ``` -#### Positional-only { data-fcm-hide="essentials" } +#### Positional-only { data-fcm-hide="essentials" cs="positional-only" } A `/` in the parameter list marks every parameter before it **positional-only** — it can only be passed by position, never by name. Most parameters don't need this restriction. It mainly shows up in library code, where locking a parameter to positional-only lets the author rename it later without breaking callers who passed it by keyword. @@ -235,7 +236,7 @@ describe("ball", 5) # by position — works describe(species="ball", length_ft=5) # TypeError — species is positional-only ``` -#### Keyword-only { data-fcm-hide="essentials" } +#### Keyword-only { data-fcm-hide="essentials" cs="keyword-only" } A `*` in the parameter list marks every parameter after it **keyword-only** — it can only be passed by name, never by position. Keyword-only parameters suit options that would be unclear as a bare positional value — `venomous=True` reads clearly at the call site, `True` alone wouldn't. @@ -247,7 +248,7 @@ describe("ball", venomous=True) # by name — works describe("ball", True) # TypeError — venomous is keyword-only ``` -### Return values +### Return values { cs="return" } `return` sends a value back to whatever called the function, instead of just printing it. `return` also exits the function immediately, skipping any code written after it. @@ -273,7 +274,7 @@ def find_species(name): result = find_species("cobra") # None — the function fell through without a return ``` -#### Multiple values { data-card-link="skip" } +#### Multiple values `return` followed by several values separated by commas [packs](../types/collections.md#packing-and-unpacking) them into a tuple as a single return value. The caller unpacks that tuple to use the values separately — see [multiple values](#multiple-values_1) under calling a function. @@ -284,7 +285,7 @@ def describe(species, length_ft): species, length = describe("ball", 5) # name = "ball", length = 5 ``` -### Keep functions focused { data-card-link="skip" } +### Keep functions focused A function should do one thing. If you find yourself describing it with "and" — "loads the species *and* saves it *and* prints a summary" — it's probably three functions. @@ -321,7 +322,7 @@ def describe(species): Both versions do the same thing — the second reads top to bottom without having to track which `if` branch you're inside. -### pass placeholder +### pass placeholder { cs="pass" } `pass` temporarily fills an empty function block so it doesn't raise a syntax error while you're not ready to write the real code yet. @@ -331,7 +332,7 @@ def describe(species): ``` -### Docstrings +### Docstrings { cs="docstrings" } A triple-quoted string as a function's first line documents what it does — most editors show it automatically when you use the function elsewhere. A **docstring** is the same triple-quoted-string trick covered on the [Foundations](../start/foundations.md#multi-line-comments-with) page, but placed as the very first line inside a function specifically to document it. Unlike a regular comment, Python actually stores a docstring (as the function's `__doc__` attribute) rather than discarding it — which is how editors are able to show it in a tooltip when you call the function elsewhere, without you needing to go find the definition. @@ -364,7 +365,7 @@ def is_too_long(species, length_ft):
    -## Calling a function +## Calling a function { cs="calling a function" } `describe("ball")` is the call — the name, followed by parentheses, is what runs the body. `"ball"` fills in `species` for that one run. Same body, run twice — only the value in `species` changes between calls. @@ -384,11 +385,11 @@ describe("burmese") # a burmese python describe("burmese") ``` -### Arguments +### Arguments { cs="arguments" } An **argument** is the actual value a caller passes in for a parameter — as opposed to a **parameter**, the placeholder name listed in a function's own definition. -#### Required +#### Required { cs="required" } By default, a call needs an argument for every parameter that doesn't have one already, supplied in the same order the parameters were listed — unless passed [by keyword](#by-keyword) instead. Leaving one out, or supplying too many, raises a `TypeError`. @@ -400,7 +401,7 @@ describe("ball", 5) # both required arguments supplied, by position describe("ball") # TypeError — missing required argument: 'length_ft' ``` -#### By keyword +#### By keyword { cs="keyword" } Passing `name=value` lets you specify arguments out of order, or skip earlier defaults. Arguments passed by position (like `describe("ball")`) must still come first; keyword arguments can follow in any order, and are matched by name instead of position. A function can also catch any number of these in one parameter — see the [`**kwargs` dict](#kwargs-dict) under defining a function. @@ -411,7 +412,7 @@ def describe(species, length_ft=5, venomous=False): describe(species="ball", venomous=True) # length_ft still uses its default ``` -#### Unpacking +#### Unpacking { cs="unpacking" } `*` and `**` also work in a function call, where they do the reverse of `*args`/`**kwargs`: instead of gathering separate arguments into one tuple or dict, they spread an existing list or dict back out into separate arguments. `*` unpacks a list or tuple into positional arguments; `**` unpacks a dict into keyword arguments. This is the call-site mirror of the [`*args` tuple](#args-tuple) and [`**kwargs` dict](#kwargs-dict) under defining a function — those gather a variable number of arguments into a tuple or dict at definition time; unpacking spreads a list, tuple, or dict back into individual arguments at the call site. @@ -423,7 +424,7 @@ details = {"species": "ball", "length_ft": 5} describe(**details) # same as describe(species="ball", length_ft=5) ``` -### Saving the return value +### Saving the return value { cs="return value" } Assign the call to a variable to keep the value `return` sent back, instead of it being discarded. `message = describe("ball")` runs `describe` with `species` set to `"ball"`, and `return` hands the built string back to the `=` that called it — `message` now holds `"a ball python"`. That's the difference from `print()`: `print()` shows a value and discards it; `return` hands the value back to be stored, passed along, or used in another expression. @@ -434,7 +435,7 @@ def describe(species): message = describe("ball") # "a ball python" — stored, not printed ``` -#### Multiple values { data-card-link="skip" } +#### Multiple values A function that [returns multiple values packed into a tuple](#multiple-values) can have them unpacked straight into multiple variables in one line at the call site. `name, length = describe(...)` unpacks the returned tuple, matching each variable to the tuple's items by position — the same as [unpacking any other tuple](../types/collections.md#packing-and-unpacking). The number of variables on the left has to match the number of values returned. @@ -449,7 +450,7 @@ name, length = describe("ball", 5) # name = "ball", length = 5
    -## Scope +## Scope { cs="scope" } A variable created inside a function is **local** — it only exists while that function is running, and isn't visible outside it. @@ -461,7 +462,7 @@ def set_species(): set_species() ``` -### Local vs global variables +### Local vs global variables { cs="local vs global" } A variable defined at the top level of a file is **global** — readable from inside any function. A function can *read* a global variable freely, but assigning to that name inside a function creates a brand-new local variable instead of changing the global one — the next section covers how to actually change a global from inside a function. @@ -518,7 +519,7 @@ def show_species():
    -## Recursion { data-fcm-hide="essentials" } +## Recursion { data-fcm-hide="essentials" cs="recursion" } A function can call itself — this is called **recursion**, an alternative to a loop for problems that break down into smaller versions of themselves. @@ -569,7 +570,7 @@ Every recursive function needs two parts: | 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](../practices/style.md#time-and-space). See [Efficiency](../practices/style.md#efficiency) for why this distinction matters. @@ -600,11 +601,11 @@ Every recursive function needs two parts:
    -## Decorators { data-fcm-hide="essentials" } +## Decorators { data-fcm-hide="essentials" cs="decorators" } **`@decorator`** lets you add behavior to a function without editing the function's own code — write the behavior once, then apply it to as many functions as you want. It's written as `@decorator_name`, placed directly above a `def`, and takes one function in, returning a function out[^callable]. -### Wrapping the call +### Wrapping the call { cs="wrapping" } A decorator can run its own code around a function call by returning a different function instead of the original — a **wrapper** that does something, calls the original, then returns. This is the shape behind most decorators you'll actually use — logging, timing, or checking permissions before letting a call through. @@ -625,7 +626,7 @@ def describe(): # here is your regular function you are decorating describe() # every function call now prints "looking up a snake...", "a python", then "found it" ``` -### Returning the original function +### Returning the original function { cs="original function" } Not every decorator needs a wrapper — the only actual requirement is returning *some* function. `catalog` below doesn't define a new one at all, it just hands back `func` itself, unchanged, so its surrounding prints only run once, the moment `describe` is defined — never again on any later call to `describe()`. @@ -651,7 +652,7 @@ def count(): print(count()) # 5 — return values pass through untouched too ``` -### Accepting arguments +### Accepting arguments { cs="arguments" } `describe` above takes no arguments, so `wrapper` didn't need to accept any either. Most functions do take arguments — `describe` normally takes a `species`, for instance — and `wrapper` has to accept whatever the decorated function needs. @@ -677,7 +678,7 @@ def total_length(*lengths): print(total_length(5, 12, 8)) # prints "called with (5, 12, 8)", then 25 — same decorator, different signature ``` -### Advanced uses +### Advanced uses { cs="identity, stacking" } ??? tip "Decorators with arguments" A decorator that needs its own settings takes those arguments one level out — a function that *returns* a decorator, instead of being one directly. This is how a decorator like Flask's `@app.route("/users")` gets its own argument (the URL path), separate from whatever function it ends up decorating. @@ -784,7 +785,7 @@ print(total_length(5, 12, 8)) # prints "called with (5, 12, 8)", then 25 —
    -## Generators { data-fcm-hide="essentials" } +## Generators { data-fcm-hide="essentials" cs="generators" } A **generator** is a function that pauses and resumes instead of running start to finish and returning once. Calling it doesn't run the body — it returns a **generator object** that produces values one at a time, only as they're asked for. @@ -798,7 +799,7 @@ for species in species_generator(): print(species) ``` -### Generator vs. a regular function { data-card-link="skip" } +### Generator vs. a regular function A regular function does all its work up front and returns one complete result; a generator pauses after each `yield` and resumes on request. @@ -828,7 +829,7 @@ len(gen) # TypeError | That result supports | Indexing, `len()`, looping more than once | Stepping forward once with `next()` or a `for` loop | | Choose it when | The caller needs the whole result — to index into it, check its length, or reuse it more than once | Values are only ever read once, start to finish, or the full sequence is too large — or too open-ended — to hold in memory all at once | -### yield vs return +### yield vs return { cs="yield" } `return` exits a function and hands back one value, all at once. `yield` hands back one value but pauses the function in place, keeping its local variables intact — the next call resumes right after that `yield` instead of starting over. @@ -845,7 +846,7 @@ next(gen) # "burmese" next(gen) # StopIteration — no values left ``` -### Memory efficiency +### Memory efficiency { cs="memory" } A generator produces values on demand instead of building the whole result up front, so it can represent a sequence too large to fit in memory — or one with no fixed end at all. @@ -863,7 +864,7 @@ next(counter) # 1 next(counter) # 2 ``` -### Generator expressions +### Generator expressions { cs="generator expressions" } Parentheses instead of brackets turn a [list comprehension](../types/collections.md#list-comprehension) into a generator expression — same filtering and transforming syntax, but values are produced lazily instead of built into a list all at once. diff --git a/docs/practices/errors.md b/docs/practices/errors.md index bdd4969..f5f28ec 100644 --- a/docs/practices/errors.md +++ b/docs/practices/errors.md @@ -1,4 +1,5 @@ --- +cheatsheet_description: Resolve bugs, read and utilize exceptions. description: >- How to read Python error messages and tracebacks, handle them with try/except, and debug with print statements or a debugger. @@ -8,9 +9,9 @@ description: >-
    -**"Errors"** occur when a line of code is impossible to run, so the program stops and displays a message with information on what went wrong and where. +**"Errors"** occur when a line of code is impossible to run, so the program stops and displays a message with information on what went wrong and where. -**"Bugs"** are the general term for errors or *any mistake* in your code, like logic errors. +**"Bugs"** are the general term for errors or *any mistake* in your code, like logic errors. **"Exceptions"** are Python's formal term for the type of error that was raised, like `KeyError` or `ValueError`. @@ -26,13 +27,13 @@ They are part of programming, and happen constantly. Based on the kind of error,
    -## Kinds of errors: +## Kinds of errors: { cs="kinds, bugs, exceptions" }
    -### Syntax errors { .pt-fake-h2 } +### Syntax errors { .pt-fake-h2 cs="syntax errors" } The code doesn't follow Python's grammar rules, so it can't read or run the file. These errors must be fixed directly. These are often incorrect punctuation, spacing, or typos. @@ -58,9 +59,9 @@ The code doesn't follow Python's grammar rules, so it can't read or run the file
    -### Runtime errors { .pt-fake-h2 } +### Runtime errors { .pt-fake-h2 cs="runtime errors" } -A **runtime error** crashes when a line of code is impossible to execute. It is grammatically correct so is able to read the file and start running, until it encounters something it can't do so it stops and gives you a specific error name. +A **runtime error** crashes when a line of code is impossible to execute. It is grammatically correct so is able to read the file and start running, until it encounters something it can't do so it stops and gives you a specific error name. Think about what programming concepts the failing line is using (data type, loop, conditional, etc), and revisit that page on this site to confirm you're applying it correctly. @@ -96,7 +97,7 @@ Think about what programming concepts the failing line is using (data type, loop
    -### Logic errors { .pt-fake-h2 } +### Logic errors { .pt-fake-h2 cs="logic errors" } A logic error is a bug Python doesn't notice, it finishes running but gives you an **unexpected result** because the reasoning itself was **inaccurate**. @@ -112,11 +113,11 @@ Think about what programming concepts you are using (data types, loops, conditio
    -## Fixing errors: +## Fixing errors: { cs="fixing" }
    -### Reading a syntax error message { .pt-fake-h2 } +### Reading a syntax error message { .pt-fake-h2 cs="syntax error message" } Red text instead of your expected output? Here's how to read it. @@ -140,7 +141,7 @@ That pointer isn't always exactly where the mistake is — an unclosed bracket o
    -### Reading a traceback { .pt-fake-h2 } +### Reading a traceback { .pt-fake-h2 cs="tracebacks" } A runtime error follows the same bottom-up pattern — but since the program actually started running, Python can show a full **traceback**: don't be intimidated by the wall of text. @@ -166,20 +167,20 @@ IndexError: list index out of range
    -### Debugging strategies { .pt-fake-h2 } +### Debugging strategies { .pt-fake-h2 cs="debugging strategies" } These general techniques help close the gap between what you think the code does and what it's actually doing. -#### Read it out loud { .pt-fake-h3 } +#### Read it out loud { .pt-fake-h3 cs="rubber duck debugging" } -Read your code line by line, out loud, saying in plain English what each line does and why. This is often called **rubber duck debugging**: putting each line into words forces you to state assumptions you'd otherwise skim past while reading silently. +Read your code line by line, out loud, saying in plain English what each line does and why. This is often called **rubber duck debugging**: putting each line into words forces you to state assumptions you'd otherwise skim past while reading silently. ```python-ref -if length < 1 and length > 20: # "If length is under 1 and length is over 20..." +if length < 1 and length > 20: # "If length is under 1 and length is over 20..." print("that length doesn't look right") # "impossible condition, should use `or` instead of `and`!" ``` -#### Print debugging { .pt-fake-h3 } +#### Print debugging { .pt-fake-h3 cs="print debugging" } ```python-ref print(type(length), length) # confirm what a value actually is, not what you assumed it was @@ -187,11 +188,11 @@ print(type(length), length) # confirm what a value actually is, not what you a Sprinkle `print()` calls between the lines you suspect, showing a variable's value (and [`type()`](../types/basics.md), if you're not sure) at that exact point in the run. This narrows down *where* your assumption about the code stopped matching reality — especially useful when nothing crashes and you're just staring at a wrong final answer, so there's no traceback pointing anywhere. Delete the `print()` calls once you've found the problem. -#### Isolate the problem { .pt-fake-h3 } +#### Isolate the problem { .pt-fake-h3 cs="isolate problems" } Comment out or delete sections of code until you find the smallest version that still shows the problem. Especially useful for syntax errors you can't obviously spot, since the pointer Python gives you isn't always exactly where the mistake is. -#### Flag as TODO/FIXME { .pt-fake-h3 } +#### Flag as TODO/FIXME { .pt-fake-h3 cs="TODO / FIXME" } ```python-ref # TODO: handle the case where length_ft is negative @@ -227,9 +228,9 @@ Not every problem gets fixed the moment you spot it — sometimes you're mid-deb
    -### Debugger tool { .pt-fake-h2 } +### Debugger tool { .pt-fake-h2 cs="debugger tool" } -A **debugger** is a tool built into most code editors that lets you pause a running program and look around, instead of only seeing what it printed after the fact. Pause your code mid-run to inspect what's happening and inspect variables — instead of only reading `print()` outputs at the end. +A **debugger** is a tool built into most code editors that lets you pause a running program and look around, instead of only seeing what it printed after the fact. Pause your code mid-run to inspect what's happening and inspect variables — instead of only reading `print()` outputs at the end. 0. **Set breakpoints.** A **breakpoint** marks a specific line where you want the program to pause while debugging, so you can inspect it. You can set as many as you want — set these *before* you start running. Click in the margin next to a line number to set one; click the same spot again to remove it — the red dot toggles off. 1. **Run in debug mode.** Look for a **"Debug"** button instead of the regular Run button. Your program will run normally until it hits the *first* breakpoint, then pauses there. @@ -287,7 +288,7 @@ A **debugger** is a tool built into most code editors that lets you pause a runn
    -### Detect errors with testing { .pt-fake-h2 } +### Detect errors with testing { .pt-fake-h2 cs="testing" } A **test** is a small script that checks your code's behavior automatically, so the mistake gets caught the moment it's introduced. @@ -300,7 +301,7 @@ def test_missing_species_returns_none(): assert get_length("reticulated python", lengths) is None ``` -[pytest](../libraries/pytest.md) is the standard tool for this in Python — a function starting with `test_` is one check, and inside it `assert` states what should be true. Running the file reports exactly which checks passed and which failed, the same way `python` reports which line of your code raised an error. +[pytest](../libraries/testing/pytest.md) is the standard tool for this in Python — a function starting with `test_` is one check, and inside it `assert` states what should be true. Running the file reports exactly which checks passed and which failed, the same way `python` reports which line of your code raised an error. Tests are especially good at catching [logic errors](#logic-errors) — where the only way to notice something's wrong is comparing the actual output against what you expected. A test does that comparison automatically, instead of relying on you to notice by eye. @@ -308,11 +309,11 @@ They're also useful for [runtime errors](#runtime-errors) — a test can exercis
    -## Handling errors: +## Handling errors: { cs="handling" }
    -### Catch with try/except { .pt-fake-h2 } +### Catch with try/except { .pt-fake-h2 cs="try/except" } `try`/`except` lets your program handle [runtime errors](#runtime-errors) and then continue without crashing. @@ -355,7 +356,7 @@ except ValueError: # separate for different handling print("length on record isn't a number") ``` -#### finally { .pt-fake-h3 } +#### finally { .pt-fake-h3 cs="else, finally" } `finally` is an optional block that always runs after `try`/`except`, whether or not an exception happened — used for cleanup that has to happen either way, like closing a file. @@ -413,7 +414,7 @@ finally:
    -### Raise an exception { .pt-fake-h2 } +### Raise an exception { .pt-fake-h2 cs="raise" } **`Raise` triggers an exception yourself,** instead of waiting for one to happen naturally — useful for stopping bad input or state before it causes a more confusing error later. @@ -437,12 +438,12 @@ except ValueError as e:
    -### Assert a condition { .pt-fake-h2 } +### Assert a condition { .pt-fake-h2 cs="assert" } **`assert` raises an `AssertionError` if a condition is False** — the same idea as `raise`, but meant for checking your own assumptions while you're still writing and testing the code, not for validating things that need to be checked every time the program is run. Catching a wrong assumption immediately, with a traceback pointing at it, is easier to debug than discovering it later as a [logic error](#logic-errors). ```python-ref -assert [boolean expression] # raises AssertionError if condition is False +assert [boolean expression] # raises AssertionError if condition is False assert [boolean expression], [message] # can add an optional message ``` diff --git a/docs/practices/style.md b/docs/practices/style.md index 0f5e4e3..d0d9f9e 100644 --- a/docs/practices/style.md +++ b/docs/practices/style.md @@ -1,4 +1,5 @@ --- +cheatsheet_description: Readable Python code, and polished UI. description: >- A Python style and code-quality checklist covering naming, formatting, docstrings, linters, and common beginner mistakes. @@ -11,7 +12,7 @@ description: >- Code that works isn't automatically code that's easy to read and maintain. - **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 +- **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 @@ -19,13 +20,13 @@ Code that works isn't automatically code that's easy to read and maintain.
    -## PEP 8 style guide +## PEP 8 style guide { cs="PEP 8" } [**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. 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 { data-fcm-hide="essentials" } +### File order { data-fcm-hide="essentials" cs="order" } 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] @@ -67,7 +68,7 @@ if __name__ == "__main__": print(is_unusually_long(ball.length_ft)) ``` -### Naming +### Naming { cs="naming" } A variable name should say what it holds — `length_ft` over `l`, `species_list` over `data`. `snake_case` and the other naming rules are covered on the [Foundations](../start/foundations.md#naming-variables) page; this is about picking a *meaningful* name within those rules, not just a valid one. @@ -78,7 +79,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 { data-fcm-hide="essentials" } +### Constants { data-fcm-hide="essentials" cs="constants" } 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. @@ -93,7 +94,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 { data-fcm-hide="essentials" } +### Quote style { data-fcm-hide="essentials" cs="quote style" } 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. @@ -102,7 +103,7 @@ print("it's a ball python") # no backslash needed print('it\'s a ball python') # works, but harder to read ``` -### Docstrings +### Docstrings { cs="docstrings" } A triple-quoted string as the first line of a function or a file documents what it does — the underlying trick is the same [multi-line comment](../start/foundations.md#multi-line-comments-with) covered on Foundations, just placed specifically as the first line. @@ -147,7 +148,7 @@ species = "ball python" length_ft = 4.5 ``` -### Indentation { data-fcm-hide="essentials" } +### Indentation { data-fcm-hide="essentials" cs="indentation" } 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). @@ -159,7 +160,7 @@ def describe(species): return f"a {species} python" # 4 spaces — PEP 8 ``` -### Blank lines +### Blank lines { cs="blank lines" } Two blank lines separate top-level function and class definitions; one blank line separates methods inside a class. @@ -178,7 +179,7 @@ def save_entry(entry): # two blank lines — PEP 8 ... ``` -### Whitespace +### Whitespace { cs="whitespace" } Put a single space around most operators (`=`, `==`, `+`, `>`), but drop it around `=` when it's a keyword argument rather than an assignment. @@ -193,7 +194,7 @@ def describe(species, length_ft=4.5): # PEP 8 ... ``` -### Comments { data-fcm-hide="essentials" } +### Comments { data-fcm-hide="essentials" cs="comments" } 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. @@ -209,13 +210,13 @@ length_ft = 4.5 # too short # PEP 8 — two spaces before, one after
    -## Linters and formatters +## Linters and formatters { cs="Linters\, 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. +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. +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. +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** @@ -238,16 +239,16 @@ A **formatter** tool (either separate, or a combined linter+formatter), actually
    -## Pythonic patterns +## Pythonic patterns { cs } -**Pythonic** code uses Python's own built-in features and standard patterns, instead of verbose work arounds. +**Pythonic** code uses Python's own built-in features and standard patterns, instead of verbose work arounds. 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. 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 +### Mutable default arguments { cs="mutable defaults" } 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. @@ -262,7 +263,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-fcm-hide="essentials" } +### Truthy checks instead of len(x) > 0 { #truthy-checks data-fcm-hide="essentials" cs="truthy checks" } Test a collection directly — a non-empty list is already truthy. @@ -274,7 +275,7 @@ if species: # Pythonic — a non-empty list is already truthy print("found some") ``` -### enumerate() instead of range(len(...)) { #enumerate-instead-of-range data-fcm-hide="essentials" } +### enumerate() instead of range(len(...)) { #enumerate-instead-of-range data-fcm-hide="essentials" cs="enumerate()" } Loop with both the index and the item at once, instead of indexing into the list by hand. @@ -286,7 +287,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 cs="is 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. @@ -303,13 +304,13 @@ if length_ft is None: # Pythonic — `is` is the correct tool for
    -## Efficiency { data-fcm-hide="essentials" } +## Efficiency { data-fcm-hide="essentials" cs } -Correct code produces the right output. +Correct code produces the right output. 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. -### Time and space { data-card-link="skip" } +### Time and space { cs="time, space" } 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. @@ -320,7 +321,7 @@ These are two **computational resources** to weigh while designing a program — | **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 +#### Big O notation { cs="big O" } Representing **O**rder of growth, the standard way to describe *how time and space grow*: @@ -335,7 +336,7 @@ Representing **O**rder of growth, the standard way to describe *how time and spa - **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" } +### Common optimizations { cs="common optimizations" } | While using | Instead of | **do this** | Because of | |---|---|---|---| @@ -350,8 +351,8 @@ Representing **O**rder of growth, the standard way to describe *how time and spa | [Lists](../types/collections.md#lists) | `insert(0, x)` / `pop(0)`
    O(n) | `append()` / `pop()` (or `deque` for the front)
    O(1) | Amortized | | [By line](../resources/files.md#by-line) | `.read()` / `.readlines()` on a large file
    O(n) space | A loop, line by line
    O(1) space | Big O | | [Recursion](../organization/functions.md#recursion) | Deep recursion
    O(n) space | A loop
    O(1) space | Big O | -| [Array operations](../libraries/numpy.md#array-operations) | A Python loop over an array
    O(n) | A vectorized NumPy operation, smaller constant
    O(n) | Constant factor | -| [Searching for a pattern](../libraries/re.md#searching-for-a-pattern) | Recompiling a regex pattern every pass
    O(n) | `re.compile()` once, reused
    O(1) | Redundant work | +| [Array operations](../libraries/data_analysis/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/utilities/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](../organization/classes.md#instance-attributes) | Many plain instances
    O(n) memory | `__slots__`, smaller constant
    O(n) memory | Constant factor | @@ -359,11 +360,11 @@ Representing **O**rder of growth, the standard way to describe *how time and spa
    -## Polished UX +## Polished UX { cs } **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 +### Input validation { cs="input validation" } 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. @@ -415,7 +416,7 @@ species = input("Enter a species: ") print(f"\nScanning... {species} detected.") ``` -### Menus +### Menus { cs="menus" } Let the user pick from a short list of options with `input()` and [`match`/`case`](../flow/conditionals.md#match-case) — a clear list of options to choose from, instead of leaving them to guess what to type. @@ -523,9 +524,9 @@ while True: See [Boxes](#boxes) below to wrap the same three options in a decorative border instead of a plain list. -### Randomize messages +### Randomize messages { cs="randomize" } -[`random.choice()`](../libraries/random.md) picks one item from a list at random, so it prints different messages every run. +[`random.choice()`](../libraries/utilities/random.md) picks one item from a list at random, so it prints different messages every run. ```python import random @@ -562,11 +563,11 @@ else:
    -## Polished UI +## Polished UI { cs } -**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. +**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 +### Escape sequences { cs="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 — used instead of typing the literal character (an actual tab, an actual line break) directly into the source. @@ -575,7 +576,7 @@ An **escape sequence** is a backslash followed by a letter, standing in for a ch | `\n` | a new line | | `\t` | a tab, as in lining up columns of output | | `\"`, `\'` | a literal quote character | -| `\\` | a literal backslash | +| `\\` | a literal backslash | | `\r` | returns the cursor to the start of the line, as in a [progress bar](#progress-bars)| ```python @@ -585,7 +586,7 @@ length_ft = 4.5 print(f"Species:\t{species}\nLength ft:\t{length_ft}") ``` -### Multi-line strings +### Multi-line strings { cs="multi-line strings" } Here are three ways to print the same four-line string: @@ -615,7 +616,7 @@ 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 +### Formatting variables { cs="formatting variables" } F-strings and format specs assemble a formatted string directly, instead of building it up by hand with `+` and manual padding. An [f-string](../types/basics.md#building-strings) — a variable's name dropped directly inside `{}` — is what turns the dashboard's bare `snake` dict into a filled-in box. A [format spec](../types/basics.md#building-strings) inside that same `{}` controls how the value looks, built from these pieces in order: @@ -644,13 +645,13 @@ print(f""" """) ``` -### Unicode symbols +### Unicode symbols { cs="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**. +**ASCII** was the original 128 character encoding for computers, standardized in the 1960s — covering English letters, digits, and punctuation on a standard keyboard. Early console styling was built around using these characters to make **ascii text and art**. Building a [raw string](../types/basics.md#building-strings) with an `r` prefix (`r"""..."""`) makes this possible to print - so that Python doesn't mistake the backslashes `\` for meaningful escape characters. @@ -658,9 +659,9 @@ There are online tools to [convert text to ascii fonts](https://patorjk.com/soft ```python print(r""" - ____ _ _ ____ _ _ _____ _ _ + ____ _ _ ____ _ _ _____ _ _ ( _ \( \/ )(_ _)( )_( )( _ )( \( ) - )___/ \ / )( ) _ ( )(_)( ) ( + )___/ \ / )( ) _ ( )(_)( ) ( (__) (__) (__) (_) (_)(_____)(_)\_) """) ``` @@ -693,35 +694,35 @@ Copy and paste these Unicode characters into your print statements, or [Browse t `→` `➔` `➜` `←` `↑` `↓` - `▶` `◀` `➤` `»` `›` `❯` `❮` `❱` `❰` - - `↳` `↲` `↰` `↱` `↵` `↴` `↪` `↩` - + `▶` `◀` `➤` `»` `›` `❯` `❮` `❱` `❰` + + `↳` `↲` `↰` `↱` `↵` `↴` `↪` `↩` + `⮕` `⬅` `⬆` `⬇` === "Checks and crosses" - `✓` `✔` `☑` `✅` - - `✖` `✗` `✘` `☒` `𐄂` `❌` `❎` + `✓` `✔` `☑` `✅` + + `✖` `✗` `✘` `☒` `𐄂` `❌` `❎` === "Bullets" - `•` `∙` `◉` `○` `◌` `◎` `●` `◦` `。` `☉` `⦾` `⦿` - + `•` `∙` `◉` `○` `◌` `◎` `●` `◦` `。` `☉` `⦾` `⦿` + `◆` `◇` `◈` `♦` `⋄` `✦` `✧` - + `☸` `✱` `✲` `✳` - - `■` `□` `☐` `▪` - + + `■` `□` `☐` `▪` + `🔵` `🟢` `🟠` `🔴` `⚫` `🟤` `🟣` `⛔` === "Special" `☺` `★` `☆` `©` `®` `™` `❤` `♡` `♥` -### Dividers +### Dividers { cs="dividers" } A row of repeated characters separates sections of output. @@ -730,7 +731,7 @@ print("survey results") print("=" * 40) ``` -### Boxes +### Boxes { cs="boxes" } Combine the [box-drawing unicode symbols](#unicode-symbols) to emphasize output, these were designed for early programs. @@ -749,10 +750,10 @@ print(""" ╚═══════════════════════╝ ❱ _ -""") +""") ``` -### Progress bars +### Progress bars { cs="progress bars" } A pause with no output looks like the program has frozen — printing something that visibly changes during the wait shows it's still working, instead of leaving the screen silent. `time.sleep()` from the [time library](../resources/modules.md#import) pauses a program for a set number of seconds. Called in a loop between `print()` calls with [`end=""`](../types/basics.md#combine) to keep the cursor on the same line, it fakes a "loading" delay. @@ -813,11 +814,11 @@ print("done!") ``` ```bash -⠏ +⠏ ``` The above character changes in place, so you see an animation cycling through the steps. -### Color styling +### Color styling { cs="background, bold, color, highlighting, underline" } 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. @@ -837,7 +838,7 @@ class a,b,d noborder
    -`\033` is the ESC character, `[` opens the code, then the code(s), and `m` closes it. +`\033` is the ESC character, `[` opens the code, then the code(s), and `m` closes it. #### Styling codes @@ -885,7 +886,7 @@ class a,b,d noborder print("\033[91mThis is bright red\033[0m") ``` -- Multiple codes separated with `;` : +- Multiple codes separated with `;` : *Combine codes inside the same escape sequence. Can't have two of the same category - e.g. no two text colors or two background colors.* @@ -894,7 +895,7 @@ class a,b,d noborder print("\033[1;32mThis is green text that is bold\033[0m") ``` -- Across multiple lines: +- Across multiple lines: *The styling stays active across multiple `print()` calls, until the reset code occurs.* diff --git a/docs/resources/files.md b/docs/resources/files.md index 4045fc8..adf71f0 100644 --- a/docs/resources/files.md +++ b/docs/resources/files.md @@ -1,10 +1,11 @@ --- +cheatsheet_description: Read and write text files on your computer. description: >- Reading and writing files in Python: opening and closing files, and working with text and other formats, with runnable examples. --- -# :material-file-document-outline:{ .lg .middle } File Read/Write +# :material-file-document-outline:{ .lg .middle } File read/write
    @@ -31,11 +32,11 @@ flowchart LR
    -## Opening and closing files +## Opening and closing files { cs="open" } `open()` returns a file object to read from or write to. -### File paths +### File paths { cs="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. @@ -68,7 +69,7 @@ On Windows, write the path with an `r` prefix (`r"C:\Users\luka\..."`) or double 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. -### with +### with { cs } `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. @@ -97,7 +98,7 @@ file.close() # easy to forget print("saved") ``` -### Modes options +### Modes options { cs="modes" } The second argument to `open()` is the **mode** — what you intend to do with the file: @@ -112,11 +113,11 @@ The second argument to `open()` is the **mode** — what you intend to do with t
    -## Read +## Read { cs="read()" } -### Modes +### Modes { cs="modes" } -#### "r" read existing +#### "r" read existing { cs="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: @@ -151,9 +152,9 @@ open("missing.txt", "r") # FileNotFoundError: [Errno 2] No such file or dire print(e) ``` -### Functions +### Functions { cs="functions" } -#### Whole file +#### Whole file { cs="read()" } `.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. @@ -186,7 +187,7 @@ with open("notes.txt", "r") as file: print(file.read(4)) ``` -#### By line +#### By line { cs="readline(), readlines()" } `.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. @@ -224,15 +225,15 @@ with open("notes.txt", "r") as file: | `.read()` / `.readlines()` | O(n) | O(n) | | Loop over the file, line by line | O(n) | O(1) | - `.read()`/`.readlines()` holds the entire file's contents in memory at once (O(n) [space](../practices/style.md#time-and-space)). Looping over the file object or calling `.readline()` repeatedly needs only enough memory for the current line, O(1) space regardless of file size. - + `.read()`/`.readlines()` holds the entire file's contents in memory at once (O(n) [space](../practices/style.md#time-and-space)). Looping over the file object or calling `.readline()` repeatedly needs only enough memory for the current line, O(1) space regardless of file size. + For a small file it doesn't matter; for a file too large to comfortably fit in memory, it's the difference between the program running and it not. See [Efficiency](../practices/style.md#efficiency) for why this distinction matters.
    -#### Seek and tell { data-fcm-hide="essentials" } +#### Seek and tell { data-fcm-hide="essentials" cs="seek(), tell()" } `.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. @@ -264,11 +265,11 @@ with open("notes.txt", "r") as file:
    -## Write +## Write { cs="write()" } -### Modes +### Modes { cs="modes" } -#### "w" overwrite +#### "w" overwrite { cs="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. @@ -305,7 +306,7 @@ with open("notes.txt", "w") as file: print(file.read()) ``` -#### "a" append +#### "a" append { cs="append" } Use `"a"` instead to add to the end, keeping the existing contents in place — compare against `"w"` above. @@ -343,7 +344,7 @@ with open("notes.txt", "r") as file: print(file.read()) ``` -#### "x" create { data-fcm-hide="essentials" } +#### "x" create { data-fcm-hide="essentials" cs="create" } `"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. @@ -374,9 +375,9 @@ open("newfile.txt", "x") # FileExistsError: [Errno 17] File exists: 'newfile print(e) ``` -### Functions +### Functions { cs="functions" } -#### Single string +#### Single string { cs="write()" } `.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"`. @@ -401,7 +402,7 @@ with open("notes.txt", "r") as file: print(file.read()) ``` -#### Multiple strings +#### Multiple strings { cs="writelines()" } `.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. @@ -429,17 +430,17 @@ with open("notes.txt", "w") as file:
    -## Related libraries +## Related libraries { cs="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. | +| :material-file-delimited-outline: [csv](../libraries/data_analysis/csv.md) | Reading and writing spreadsheets. | +| :material-code-json: [json](../libraries/apis/json.md) | Reading and writing JSON data: nested dicts and lists, saved to a file or a string. | +| :material-image-outline: [Pillow](../libraries/images/pillow.md#opening-and-saving-images) | Opening, editing, and saving images, built around one Image object. | +| :material-face-recognition: [OpenCV](../libraries/computer_vision/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/data_analysis/matplotlib.md#saving-a-figure) | Charts and plots: line, bar, and scatter, built directly from plain Python data. | +| :material-application-outline: [tkinter](../libraries/desktop_uis/tkinter.md#file-dialogs) | Creating desktop applications: text, buttons, dropdowns, forms, output, etc. |
    diff --git a/docs/resources/modules.md b/docs/resources/modules.md index f2ecb47..365d215 100644 --- a/docs/resources/modules.md +++ b/docs/resources/modules.md @@ -1,12 +1,13 @@ --- +cheatsheet_description: Splitting code across files, and using someone else's code. description: How Python modules and imports work, including writing and importing your own, with runnable examples. --- -# :material-import:{ .lg .middle } Modules & Imports +# :material-import:{ .lg .middle } Modules & imports
    -## Modules vs packages vs libraries +## Modules vs packages vs libraries { cs="module\, package\, library" } A **module** is a Python file. Any `.py` file can be imported and used by another one. As a project grows, splitting related functions and classes into their own files, then importing between them, keeps any one file from becoming unmanageable. @@ -30,15 +31,15 @@ flowchart LR
    -**Library** is the informal umbrella term for either: a single module or a whole package — that's organized to be reused across projects. The [Libraries page](../libraries/index.md) highlights a few common published libraries. +**Library** is the informal umbrella term for either: a single module or a whole package — that's organized to be reused across projects. The [Libraries page](../libraries/index.md) highlights a few common published libraries.
    -## Importing modules +## Importing modules { cs="import" } -### import +### import { cs } `import` makes a module's code available under its own name, so you call things through it with a `.` — `random.randint(...)`, not just `randint(...)`. @@ -48,7 +49,7 @@ import random print(random.randint(1, 10)) ``` -### as +### as { cs } `as` gives the imported module a different name to call it by — handy for a long name you'd rather type shorter, or one that collides with something else in the file. @@ -58,11 +59,11 @@ import random as rnd print(rnd.randint(1, 10)) ``` -### from +### from { cs } -**Specify a named piece that can be used directly:** Naming the exact names you need is considered best practice, as it purposefully only imports the pieces you are using. +**Specify a named piece that can be used directly:** Naming the exact names you need is considered best practice, as it purposefully only imports the pieces you are using. -#### Packages +#### Packages { cs="packages" } `from` package `import` module @@ -119,7 +120,7 @@ from snake_helpers import describe # describe is a function inside snake_hel print(describe("ball")) ``` -#### Nested paths +#### Nested paths { cs="nested paths" } `from` package.module `import` function/class/variable @@ -161,7 +162,7 @@ print(join("snakes", "ball_python.txt")) randint(1, 10) # works, but where did randint come from? ``` -### order of multiple imports +### order of multiple imports { cs="import order" } Imports conventionally go near the top of the file, grouped in order: Python's own standard library first, then third-party packages, then your own local files — with a blank line between each group. @@ -177,7 +178,7 @@ import snake_data # your own file
    -## Creating your own module +## Creating your own module { cs="your own module" } Your own `.py` files import the same way — use the filename, without `.py`, as the module name. @@ -198,7 +199,7 @@ print(snake_helpers.describe("ball")) Avoid naming your own file after a library you use — your file named `random.py` shadows Python's own `random` module for anything else in that project. See the [file naming rules](../start/workspace.md#step-2-write-and-run-a-python-file) for more. -### The main guard +### The main guard { cs="main guard" } `if __name__ == "__main__":` controls what runs only when a file is run directly — not when it's imported into another file. @@ -210,7 +211,7 @@ if __name__ == "__main__": print(describe("ball")) ``` -Wrapping your "do the actual work" code in this check means another file can `import` yours — to reuse a function, say — without that main code running too. +Wrapping your "do the actual work" code in this check means another file can `import` yours — to reuse a function, say — without that main code running too.
    diff --git a/docs/start/foundations.md b/docs/start/foundations.md index 3676b5b..fcc3ae6 100644 --- a/docs/start/foundations.md +++ b/docs/start/foundations.md @@ -1,4 +1,5 @@ --- +cheatsheet_description: Storing, displaying, and inputting values. description: >- The basics every Python program starts with: variables, print(), input(), and comments, with runnable examples. @@ -8,29 +9,29 @@ description: >-
    -## Tips for getting started +## Tips for getting started { cs="tips for getting started" } -- **[Setup](workspace.md) your workspace first** so you can run Python on your computer and edit Python files. -- **Work through the pages in order.** +- **[Setup](workspace.md) your workspace first** so you can run Python on your computer and edit Python files. +- **Work through the pages in order.** - **Type the examples yourself**, and actually click **Run** on the runnable blocks and edit them — change a value, rerun, see what changes. That's where a concept actually sticks, not from reading it. - **Errors are a normal, constant part of writing code, not a sign you did something wrong.** Once you hit your first one, the [Errors](../practices/errors.md) page contains [strategies for resolving them](../practices/errors.md#debugging-strategies). - [Read it out loud](../practices/errors.md#read-it-out-loud) - [Print debugging](../practices/errors.md#print-debugging) - [Isolate the problem](../practices/errors.md#isolate-the-problem) -- **Try building something small.** Once you've read through [Conditionals](../flow/conditionals.md) and [Loops](../flow/loops.md) you already have enough to write a program. +- **Try building something small.** Once you've read through [Conditionals](../flow/conditionals.md) and [Loops](../flow/loops.md) you already have enough to write a program. - The homepage has more on [using AI to help you learn](../index.md).
    -## Print function +## Print function { cs="print" } -### What do you see when a program runs? { data-card-link="skip" } +### What do you see when a program runs? When a Python program is running, it won't show you anything on its own — it runs silently. -That's a problem for you as the developer — without some way to look inside, you can't follow along with what it's actually doing as it runs. +That's a problem for you as the developer — without some way to look inside, you can't follow along with what it's actually doing as it runs.
    @@ -74,7 +75,7 @@ flowchart LR Code editors have an **output** window at the bottom that shows the print statements as the program runs. -### Structure of a print() statement { data-card-link="skip" } +### Structure of a print() statement `print` is a [function](../organization/functions.md) — a named, reusable piece of code that does something when you "call" it by name. These building blocks are all you need to use `print()`: @@ -97,7 +98,7 @@ print(4.5) # when printing a number, you do not need quotes Most sections on this site end with a collapsed block like the one below — open it, click **Run**, and try editing the code and running it again. -### Escape sequences +### Escape sequences { cs="escape sequences" } `\n` and `\t` are **escape sequences** — `\n` inserts a line break, `\t` a tab — so a single `print()` call can space out multi-line or columned output. @@ -122,7 +123,7 @@ print("survey results") print("=" * 40) ``` -### Going further { data-card-link="skip" } +### Going further ??? run "Run a print() example" All the examples above, combined into one script: @@ -150,9 +151,9 @@ print("=" * 40)
    -## Variables +## Variables { cs="variables" } -### How do variables work? { data-card-link="skip" } +### How do variables work? A variable stores a value under a name so you can refer to that value again later instead of retyping it. @@ -175,11 +176,11 @@ flowchart TB Now you can reference the variable `species` and it will be equal to the value `burmese`. -Saving a new variable (we call this "assigning a variable") follows this format: +Saving a new variable (we call this "assigning a variable") follows this format: `[variable name]` **`=`** `[value]` -### Naming variables +### Naming variables { cs="naming" } `snake_case` (lowercase words separated by underscores) is the standard format for variable names. @@ -190,9 +191,9 @@ species_2 = "burmese python" # valid **Variable naming rules:** -1. **Only contains letters, underscores, and numbers** +1. **Only contains letters, underscores, and numbers** - Standard formatting is to use `snake_case` (all lowercase, separated with underscores). + Standard formatting is to use `snake_case` (all lowercase, separated with underscores). 2. **Can't start with a number** @@ -200,7 +201,7 @@ species_2 = "burmese python" # valid Standard convention is that variables are always lower case. `Species` and `species` would be two different variables. -4. **Don't use a reserved keyword** +4. **Don't use a reserved keyword** There are a handful of "keywords" that are reserved by Python to do specific things, so they can't be used elsewhere in your code. Run this code to get a list of all reserved keywords: @@ -208,15 +209,15 @@ species_2 = "burmese python" # valid help("keywords") ``` -5. **Don't use a library's name** +5. **Don't use a library's name** Naming a file `random.py` or `math.py` in a project makes `import random` elsewhere in that same project import your file instead of Python's actual `random` library, which is a confusing bug to track down. Run this code to get a list of all reserved library names: - + ```python help("modules") ``` -### Reassigning a variable +### Reassigning a variable { cs="reassigning" } You can update the value of an existing variable by setting it equal to something else. @@ -263,7 +264,7 @@ species = "burmese python" # replaces the old value entirely print(a, b) ``` -### Printing variables { data-card-link="skip" } +### Printing variables { cs="printing" } **To print a single variable:** @@ -300,7 +301,7 @@ Commas are usually the easier choice for a quick print. Pass `sep="..."` to chan Once you're comfortable with the basics here, the [Collections](../types/collections.md#list-operations) page covers printing the contents of a list or dict. -### Building a string manually { data-card-link="skip" } +### Building a string manually Come back to this once you've read the [Types](../types/basics.md) page. @@ -330,7 +331,7 @@ For building a full sentence out of text and variables, an [f-string](../types/b print(species, length_ft, sep=", ") ``` -### Variables and types { data-card-link="skip" } +### Variables and types { cs="types" } Come back to this once you've read the [Types](../types/basics.md) page. @@ -347,7 +348,7 @@ Other languages fix a variable to one type permanently at creation; Python doesn
    -## Expressions and statements +## Expressions and statements { cs="expressions and statements" } Every line of Python code is either an **expression** or a **statement**. An expression is anything that evaluates to a value — `2 + 3`, `species`, `species == "burmese"`. A statement is a complete instruction — an assignment, a `print()` call, an `if` statement's condition (covered on the [Conditionals](../flow/conditionals.md#if-elif-else) page) — and it's usually built out of one or more expressions. @@ -387,7 +388,7 @@ The distinction is about what's allowed where: an expression can go anywhere Pyt
    -## Input function +## Input function { cs="input" } `input()` allows the program to get typed input from the user @@ -402,7 +403,7 @@ print(first_name) The text inside the parentheses — `"What's your first name? "` — is the **prompt**: a message shown before the program waits, so the person knows what to type. -### Structure of an input() statement { data-card-link="skip" } +### Structure of an input() statement `input` is a **function**, same as `print` — these are the same building blocks, just with a variable assignment at the beginning to save what the user inputs: @@ -420,16 +421,16 @@ class eq,i,o,c,q1,q2 punct **Prompt format:** -Input prompts often have a `?` or `:` at the end. +Input prompts often have a `?` or `:` at the end. ```python-ref first_name = input("What's your first name? ") # can use a ? last_name = input("Enter your last name: ") # or can use a : ``` -They generally have an extra space before the last `"` — otherwise when the user starts typing their typing will be right up against the prompt with no gap, so it is harder to read. +They generally have an extra space before the last `"` — otherwise when the user starts typing their typing will be right up against the prompt with no gap, so it is harder to read. -### Saving what the user types { data-card-link="skip" } +### Saving what the user types `input()` has to be assigned to a variable, or whatever was typed is thrown away — there's no other way to get back to it once the line finishes running. @@ -441,7 +442,7 @@ name = input("What's your name? ") # saved to the variable name print("Hello,", name) # now a usable variable ``` -### Converting input to a number { data-card-link="skip" } +### Converting input to a number Come back to this once you've read the [Types](../types/basics.md) page. @@ -461,11 +462,11 @@ print(age + 1) # 9 — works fine
    -## Comments +## Comments { cs="comments" } -### Single-line comments with \# { data-card-link="skip" } +### Single-line comments with \# { cs="#, FIXME, TODO" } -A `#` marks the rest of a line as a comment — so Python ignores it. There are multiple reasons for this: +A `#` marks the rest of a line as a comment — so Python ignores it. There are multiple reasons for this: 1. **Annotate the code for yourself, explaining *why* or *how* it works.** @@ -476,13 +477,13 @@ A `#` marks the rest of a line as a comment — so Python ignores it. There are print(species, length_ft) ``` - A comment that just restates the code in English (`# set length_ft to 4.5`) adds noise, not information — the code already says that. What's worth writing down is the reasoning the code itself can't show. + A comment that just restates the code in English (`# set length_ft to 4.5`) adds noise, not information — the code already says that. What's worth writing down is the reasoning the code itself can't show. This also works in reverse: if you don't fully understand a piece of code yet — maybe you copied it from somewhere, or it's still new to you — leaving yourself a comment explaining it is genuinely useful. 2. **Temporarily disable code** - Adding a `#` in front of a line stops it from running, without deleting it. Select multiple lines to comment out a whole block at once. + Adding a `#` in front of a line stops it from running, without deleting it. Select multiple lines to comment out a whole block at once. ```python-ref species = "ball python" @@ -513,7 +514,7 @@ A `#` marks the rest of a line as a comment — so Python ignores it. There are length_ft = 4.5 ``` - `TODO` is a word programmers agree to write in a comment to mean "come back to this." + `TODO` is a word programmers agree to write in a comment to mean "come back to this." `FIXME` is a common variant for flagging something that's actively broken, rather than just unfinished. @@ -523,7 +524,7 @@ A `#` marks the rest of a line as a comment — so Python ignores it. There are - **VS Code** needs an extension for this — [Todo Tree](https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.todo-tree) is the most popular one, and adds a sidebar tree view of every tagged comment in your workspace. - **Thonny and IDLE** have no built-in equivalent — `TODO` still works as a plain comment, just without the aggregated list. -### Multi-line comments with """ { data-card-link="skip" } +### Multi-line comments with """ { cs=""""" } A triple-quoted string on its own line acts like a comment spanning several lines. diff --git a/docs/start/workspace.md b/docs/start/workspace.md index 65be327..3cd47d3 100644 --- a/docs/start/workspace.md +++ b/docs/start/workspace.md @@ -1,16 +1,17 @@ --- +cheatsheet_description: Write Python on your computer. description: >- How to install Python, pick a code editor, and run your first .py file — a step-by-step setup guide. --- -# :material-monitor:{ .lg .middle } Workspace Setup +# :material-monitor:{ .lg .middle } Workspace setup
    -## Step 0: Install Python +## Step 0: Install Python { cs="install, download, version" } -0. Open your Terminal application *(Terminal on Mac/Linux, Command Prompt or PowerShell on Windows)* +0. Open your Terminal application *(Terminal on Mac/Linux, Command Prompt or PowerShell on Windows)* 1. Type this command and press ++return++ to "run" it: @@ -19,9 +20,9 @@ description: >- ``` ??? success "If it shows `Python 3.x.x`, Python is installed!" - If you ever decide to run your files from the Terminal later you'll use the command `python`. + If you ever decide to run your files from the Terminal later you'll use the command `python`. - Skip to Step 1: Pick a code editor. + Skip to Step 1: Pick a code editor. ??? info "If you see `Python 2.x.x`" Python 2 is installed. Python 2 reached end of life in 2020 and is no longer maintained. @@ -39,13 +40,13 @@ description: >- === "macOS" [python.org/downloads](https://python.org/downloads) - + === "Windows" [python.org/downloads](https://python.org/downloads) Check **"Add python.exe to PATH"** on the first install screen — if you skip this, the terminal won't recognize `python` - + === "Linux" Usually already installed. If `python3 --version` failed, install via your package manager (e.g. `sudo apt install python3`) @@ -54,7 +55,7 @@ description: >-
    -## Step 1: Pick an application to write code in +## Step 1: Pick an application to write code in { cs="code editors, IDLE, Pycharm, Thonny, VS Code" } A **code editor** or an **IDE** ("Integrated Development Environment") is a text editor designed specifically for writing code — it's like Microsoft Word for programming. @@ -74,15 +75,15 @@ Download one of the **free** code editors below. You can always switch later. | **PyCharm Community** |
    • Complete Python IDE
    • Everything built-in out of the box
    • Many panels/menus can feel overwhelming at first
    | Professionals | JetBrains | [Windows](https://www.jetbrains.com/pycharm/download/) / [macOS](https://www.jetbrains.com/pycharm/download/) / [Linux](https://www.jetbrains.com/pycharm/download/) | ??? info "What does "open-source" mean?" - The source code that it is built from is publicly available for anyone to see, modify, and improve. - + The source code that it is built from is publicly available for anyone to see, modify, and improve. + *Thonny* is maintained by volunteers in the open-source community. *VS Code* and *PyCharm* are made by companies but also have open-source elements.
    -## Step 2: Write and run a Python file +## Step 2: Write and run a Python file { cs="how to write and run .py file, file naming" } Now that you have Python installed and a code editor picked, you're ready to write actual Python code. @@ -92,8 +93,8 @@ Now that you have Python installed and a code editor picked, you're ready to wri ```python-ref print("Hello, World!") ``` -3. Click the **Run button** (usually a green play icon or arrow) — most editors save your file automatically when you click Run, so there's no separate save step. -4. Find the output window in the application where it says `Hello, World!`, it should pop up on its own. +3. Click the **Run button** (usually a green play icon or arrow) — most editors save your file automatically when you click Run, so there's no separate save step. +4. Find the output window in the application where it says `Hello, World!`, it should pop up on its own. That's it! You've written and run your first Python program. From here, you can modify the code, run it again, and [work through the rest of this guide](foundations.md#tips-for-getting-started) to keep building your Python programming skills. @@ -107,38 +108,38 @@ That's it! You've written and run your first Python program. From here, you can my script.py # invalid — no spaces ``` - 1. **Ends in .py** - + 1. **Ends in .py** + This is what tells your application to treat the file as Python code — the Run button, syntax highlighting, and imports all depend on the extension being there. - - 2. **Only letters, underscores, and numbers** — but it can't start with a number. - + + 2. **Only letters, underscores, and numbers** — but it can't start with a number. + Standard formatting is to use `snake_case` (all lowercase, separated with underscores). Python is case-sensitive (Species.py and species.py would be two different files). - - 3. **No hyphens** - + + 3. **No hyphens** + Even though `my-script.py` will run fine on its own, if you need to later `import my-script` it will be invalid syntax because Python reads the hyphen as subtraction. - - 4. **No spaces** - + + 4. **No spaces** + They will break imports and makes running the file from the terminal require extra quoting. - - 5. **Don't use a reserved keyword** - + + 5. **Don't use a reserved keyword** + There are a handful of "keywords" that are reserved by Python to do specific things, so they can't be used elsewhere in your code. Run this code to get a list of all reserved keywords: ```python help("keywords") ``` - - 6. **Don't use a library's name** - + + 6. **Don't use a library's name** + Naming a file `random.py` or `math.py` in a project makes `import random` elsewhere in that same project import your file instead of Python's actual `random` library, which is a confusing bug to track down. Run this code to get a list of all reserved library names: - + ```python help("modules") ``` - + ??? tip "Reading error messages" When you see red error text, the [Errors](../practices/errors.md#reading-a-traceback) page covers how to read it. @@ -147,15 +148,15 @@ That's it! You've written and run your first Python program. From here, you can
    -## Using the terminal { data-fcm-hide="essentials" } +## Using the terminal { data-fcm-hide="essentials" cs="Terminal, cd, ls, pwd, shortcuts" } -The terminal is a text-based way to navigate your computer's files and run programs. +The terminal is a text-based way to navigate your computer's files and run programs. It's good for running Python files that are already finished — either your own, or someone else's — without needing to open them in an editor. It's also handy for quickly re-running the same command over and over while testing. -0. Open the terminal +0. Open the terminal - You can either use a dedicated terminal application (Terminal on Mac/Linux, Command Prompt or PowerShell on Windows), or if your code editor application has a terminal window you can use that. + You can either use a dedicated terminal application (Terminal on Mac/Linux, Command Prompt or PowerShell on Windows), or if your code editor application has a terminal window you can use that. 1. Navigate to the folder ("location") your Python file is saved in using these commands: @@ -167,7 +168,7 @@ It's good for running Python files that are already finished — either your own ``` Here's an example: - + ```bash $ pwd /Users/luka @@ -192,47 +193,47 @@ It's good for running Python files that are already finished — either your own python3 script.py ``` -3. To stop a running program: ++ctrl+c++ +3. To stop a running program: ++ctrl+c++ -4. You can now run a Python file again, or a different command. +4. You can now run a Python file again, or a different command. !!! warning "Be careful what you send in the terminal" The terminal has no undo, and no confirmation prompt for most commands — it does exactly what you type, even if that means deleting or overwriting something permanently. Never paste a command you don't fully understand, especially from a random webpage or AI. - + Use **extreme caution** with `rm`, `sudo`, or a file path you didn't type yourself. ??? tip "Terminal shortcuts" - 1. **Auto-complete file/folder names:** - - Start typing a file or folder name and press ++tab++ — the terminal will autoc-omplete it for you. For example, if you type `cd Doc` then press Tab, it becomes `cd Documents/`. - + 1. **Auto-complete file/folder names:** + + Start typing a file or folder name and press ++tab++ — the terminal will autoc-omplete it for you. For example, if you type `cd Doc` then press Tab, it becomes `cd Documents/`. + If there are multiple matches, press ++tab++ again to cycle through them, or type more letters so that there is only one option it could be and then ++tab++ again. - 2. **Auto-fill previous commands:** - - ++up++ shows your last command, and press it again to go further back. - - ++down++ then moves forward through the history. - + 2. **Auto-fill previous commands:** + + ++up++ shows your last command, and press it again to go further back. + + ++down++ then moves forward through the history. + You can this press return to send that command without needing to type it out. This saves typing when you want to send the same command(s) multiple times. 3. **Give the full path in one command:** - `~/` aka "tilde" = your home folder. - + `~/` aka "tilde" = your home folder. + Specify the complete path with `cd ~/Documents/my_folder/my_project`. You can also run the file with one command: `python path/to/script.py`. - + 4. **Stop a running Python file:** - ++ctrl+c++ + ++ctrl+c++
    -## Virtual environments { data-fcm-hide="essentials" } +## Virtual environments { data-fcm-hide="essentials" cs="virtual environments, activate, pip, requirements.txt, venv" } Sometimes you'll want to install [external libraries](../libraries/index.md) for your project. A **virtual environment** keeps each project's installed libraries in their own separate folder instead of installing them onto your computer. @@ -245,7 +246,7 @@ Sometimes you'll want to install [external libraries](../libraries/index.md) for **To setup and run a virtual environment:** -0. [Open the terminal](#using-the-terminal) and navigate to your project folder +0. [Open the terminal](#using-the-terminal) and navigate to your project folder 1. Create a `venv` folder holding a private copy of Python and its libraries. This only needs to happen the first time you run your project. @@ -253,7 +254,7 @@ Sometimes you'll want to install [external libraries](../libraries/index.md) for python -m venv venv # or use python3, depending on what you saw in Step 0 above ``` -2. Activate it, you need to do this every time you open a new terminal window: +2. Activate it, you need to do this every time you open a new terminal window: === "macOS/Linux" diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 9dfb6b5..6d266dc 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -393,91 +393,19 @@ html:focus-within::-webkit-scrollbar-thumb { 100% { background-position: 0% 50%; } } -/* The logo only ever linked home with no other purpose, so - cheatsheet_button.js's "Cheatsheet" pill replaces it in the header's - top-left slot instead of sitting alongside it — hidden here, not - removed from the DOM, since Material still generates it normally. */ -.md-header__button.md-logo { - display: none; -} - -/* "Cheatsheet" link injected by cheatsheet_button.js, in the logo's old - spot (leading the site title) — an explicit text shortcut to the - homepage (already this site's compact quick-reference dashboard) rather - than relying on the logo image alone reading as clickable. Deliberately - reuses the Essentials/Advanced toggle's own technique (a currentColor - pill fill + --pt-bg text flip when active) rather than hardcoded colors, - so it adapts across light/dark mode exactly the way that toggle does — - currentColor here is .md-header's own `color: var(--pt-ink)`, inherited, - not set locally. */ -.pt-cheatsheet-link { - display: inline-flex; - align-items: center; - margin-right: 0.6rem; - padding: 0.15rem 0.6rem; - /* Matches .pt-simplify-toggle's own resting track (border + background) - below, so the inactive pill reads as the same kind of control rather - than plain text — var(--pt-ink) directly instead of currentColor, - sidestepping the self-reference risk noted below for the active state. */ - border: 0.05rem solid var(--pt-ink); - border-radius: 1rem; - background-color: var(--pt-bg); - font-size: 0.6rem; - font-weight: 700; - letter-spacing: 0.02em; - color: currentColor; - text-decoration: none; - white-space: nowrap; -} - -/* Material Symbols "dashboard" (viewBox 0 -960 960 960), same mask-image - technique as .pt-simplify-option's icons above — an asymmetric mixed-size - panel shape, matching the homepage's own grid (its library boxes are - genuinely different widths, not a uniform grid — see the "External - files and resources" grid-math notes in CLAUDE.md) rather than a plain - symmetric grid icon. Fetched from Google's material-design-icons source - directly (not hand-drawn) to keep the path data exact. Leads the word - (::before, not ::after), matching the icon-then-text order used by the - Essentials/Advanced toggle, the theme toggle, and the toast. */ -.pt-cheatsheet-link::before { - content: ""; - display: inline-block; - width: 0.7rem; - height: 0.7rem; - flex: none; - margin-right: 0.3rem; - background-color: currentColor; - -webkit-mask-repeat: no-repeat; - mask-repeat: no-repeat; - -webkit-mask-size: contain; - mask-size: contain; - -webkit-mask-position: center; - mask-position: center; - -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -960 960 960'%3E%3Cpath d='M520-600v-240h320v240H520ZM120-440v-400h320v400H120Zm400 320v-400h320v400H520Zm-400 0v-240h320v240H120Zm80-400h160v-240H200v240Zm400 320h160v-240H600v240Zm0-480h160v-80H600v80ZM200-200h160v-80H200v80Zm160-320Zm240-160Zm0 240ZM360-280Z'/%3E%3C/svg%3E"); - mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -960 960 960'%3E%3Cpath d='M520-600v-240h320v240H520ZM120-440v-400h320v400H120Zm400 320v-400h320v400H520Zm-400 0v-240h320v240H120Zm80-400h160v-240H200v240Zm400 320h160v-240H600v240Zm0-480h160v-80H600v80ZM200-200h160v-80H200v80Zm160-320Zm240-160Zm0 240ZM360-280Z'/%3E%3C/svg%3E"); -} - -.pt-cheatsheet-link:hover, -.pt-cheatsheet-link:focus-visible { - text-decoration: underline; -} - -/* Not background-color: currentColor here — the toggle's pill and its text - are two separate elements, so its highlight span can safely use - currentColor (the *inherited* color) while a different element's text - flips to --pt-bg. Collapsed into one element like this, currentColor - would resolve against this same rule's own `color: var(--pt-bg)` below - it (CSS uses the final computed color, not declaration order) — pill and - text would end up identical, text invisible. --pt-ink directly sidesteps - that self-reference entirely. */ -.pt-cheatsheet-link--active { - background-color: var(--pt-ink); - color: var(--pt-bg); -} - -.pt-cheatsheet-link--active:hover, -.pt-cheatsheet-link--active:focus-visible { - text-decoration: none; +/* "Cheatsheet" header button (mkdocs-cheatsheet plugin, which also hides + the logo it replaces). Matches the Essentials/Advanced toggle: an ink + outline at rest, filled ink with cream text when on the homepage. */ +/* `:root, [data-md-color-scheme]`, not `:root` alone: Material sets the + scheme on , so --pt-ink/--pt-bg only take their dark values there + (see CLAUDE.md "Custom color palette on Material"). */ +:root, +[data-md-color-scheme] { + --md-cheatsheet-button-fg: var(--pt-ink); + --md-cheatsheet-button-bg: var(--pt-bg); + --md-cheatsheet-button-border: var(--pt-ink); + --md-cheatsheet-button-active-fg: var(--pt-bg); + --md-cheatsheet-button-active-bg: var(--pt-ink); } .md-tabs { @@ -979,17 +907,17 @@ input:checked + .md-consent__settings { /* Library cards: same base background as the core cards, plus a 1px border in the corner-wedge colour (the section-container background). In dark mode a translucent black overlay also nudges them a shade darker. */ -.pt-category--wide .grid.cards > ul > li { +.library-grid .md-cheatsheet__group .grid.cards > ul > li { border: 1px solid var(--pt-section-bg); } -[data-md-color-scheme="slate"] .pt-category--wide .grid.cards > ul > li { +[data-md-color-scheme="slate"] .library-grid .md-cheatsheet__group .grid.cards > ul > li { background-image: linear-gradient(rgba(0, 0, 0, 0.22), rgba(0, 0, 0, 0.22)); } /* ...and a filled diagonal corner ("dog-ear") in the old fill color at the top-right, sitting behind the built-in / third-party badge (z-index 2). */ -.pt-category--wide .grid.cards > ul > li::before { +.library-grid .md-cheatsheet__group .grid.cards > ul > li::before { content: ""; position: absolute; top: 0; @@ -1037,23 +965,10 @@ input:checked + .md-consent__settings { margin-bottom: 0.2em; } -/* Comma-separate the keyword tags in each card row so the row reads as a list. - The gap between links comes from the existing whitespace, so no trailing - space in the content. The bold lead keyword is followed by a hardcoded ": " - in index.md instead (e.g. "integers: + - * / **"), so it's exempt. */ -.md-typeset .grid.cards > ul > li > p:nth-of-type(n+3) a:not(:last-child)::after { - content: ","; - color: var(--pt-text-muted); -} - -.md-typeset .grid.cards > ul > li > p:nth-of-type(n+3) a:first-child:has(strong)::after { - content: none; -} - .md-typeset .grid.cards > ul > li > hr { margin: 0.4em 0; } -.md-typeset h4.pt-homepage-heading { +.md-typeset h4.md-cheatsheet__group-title { margin: 0.3rem 0 0.1rem; } @@ -1322,79 +1237,66 @@ input:checked + .md-consent__settings { font-weight: 400; } -/* The mkdocs-audience-toggle plugin hides a marked element directly - (see mkdocs.yml) — but a homepage card's data-fcm-hide="essentials" marker - sits on its first paragraph (the icon/title line attr_list attaches to, - not the
  • — python-markdown's attr_list can't target a list item that - has more than one paragraph), so the plugin only hides that one line, - leaving the rest of the card behind as an empty box. Hide the whole - card here instead, keyed off the same marker + the mode the plugin - itself sets on . */ -html[data-fcm-mode="essentials"] .grid.cards > ul > li:has(> p[data-fcm-hide~="essentials"]) { - display: none; +.library-grid > .md-cheatsheet__group { + grid-column: 1 / -1; } -.pt-category-grid { +.md-cheatsheet { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.6rem; } @media (min-width: 45em) { - .pt-category:not(.pt-category--wide) .grid.cards > ul { + .md-cheatsheet:not(.library-grid) .md-cheatsheet__group .grid.cards > ul { grid-template-columns: repeat(2, 1fr); } } /* On a wide screen the whole homepage grid is 4 columns wide. The upper category boxes each span 2 (so still 2-per-row, 2 cards each = 4 across); the - library section boxes span their own card count (`pt-lib--N`), so the rows - pack to exactly 4 library cards — Utilities(5, alone — 5 doesn't divide - evenly into the 4-column grid, so it takes a full row on its own), - Data analysis(4), APIs(2)+Image editing(1)+Desktop UIs(1), - Testing(1)+Computer vision(1) trailing. Order matters here: grid + library section boxes span their own card count (the cheatsheet plugin's + `md-cheatsheet__group--cards-N`), so the rows + pack to exactly 4 library cards — Utilities(6, capped to a full row), + Data analysis(4), APIs(2)+Web(1)+Images(1), then + Computer vision+Desktop UIs+Games+Testing(1 each). Order matters here: grid auto-placement is sparse (no `dense`), so a box that doesn't fit the remaining space in its row leaves that space empty rather than letting a - later, smaller box backfill it — keep same-row groups adjacent in the - markdown, and put any leftover under-4 remainder last. The library grids + later, smaller box backfill it — keep same-row groups adjacent in + mkdocs.yml's nav (the cheatsheet follows nav order), and put any leftover under-4 remainder last. The library grids keep the default auto-fit/minmax, which lands on the right column count inside a box that's already sized to its contents. */ @media (min-width: 60em) { - .pt-category-grid { + .md-cheatsheet { grid-template-columns: repeat(4, 1fr); } - .pt-category-grid > h1 { - grid-column: 1 / -1; - } - - .pt-category-grid > .pt-category:not(.pt-category--wide) { + .md-cheatsheet:not(.library-grid) > .md-cheatsheet__group { grid-column: span 2; } - /* compound selector so these beat `.pt-category--wide { grid-column: 1 / -1 }` */ - .pt-category--wide.pt-lib--1 { grid-column: span 1; } - .pt-category--wide.pt-lib--2 { grid-column: span 2; } - .pt-category--wide.pt-lib--3 { grid-column: span 3; } - .pt-category--wide.pt-lib--4 { grid-column: span 4; } - .pt-category--wide.pt-lib--5 { grid-column: span 4; } /* caps at the grid's own width, same as 4 */ + /* --cards-N is emitted by the cheatsheet plugin per group */ + .library-grid > .md-cheatsheet__group--cards-1 { grid-column: span 1; } + .library-grid > .md-cheatsheet__group--cards-2 { grid-column: span 2; } + .library-grid > .md-cheatsheet__group--cards-3 { grid-column: span 3; } + .library-grid > [class*="md-cheatsheet__group--cards-"]:not(.md-cheatsheet__group--cards-1):not(.md-cheatsheet__group--cards-2):not(.md-cheatsheet__group--cards-3) { grid-column: span 4; } /* caps at the grid's own width */ - /* pt-lib--N above is sized for the full card count; Essentials mode + /* --cards-N above is sized for the full card count; Essentials mode hides some, leaving boxes too wide. theme_toggle.js recomputes the - visible count into --pt-lib-span, overriding pt-lib--N here (same + visible count into --library-span, overriding --cards-N here (same specificity, later in source) while active. */ - html[data-fcm-mode="essentials"] .pt-category--wide { - grid-column: span var(--pt-lib-span, 4); + html[data-fcm-mode="essentials"] .library-grid > .md-cheatsheet__group { + grid-column: span var(--library-span, 4); } } @media (max-width: 45em) { - .pt-category-grid { + .md-cheatsheet { grid-template-columns: 1fr; } } -.pt-category { +.md-cheatsheet__group { padding: 0.8rem; background-color: var(--pt-section-bg); border-radius: 0.3rem; @@ -1406,7 +1308,7 @@ html[data-fcm-mode="essentials"] .grid.cards > ul > li:has(> p[data-fcm-hide~="e background-color: var(--pt-panel); } -.pt-category > h4.pt-homepage-heading { +.md-cheatsheet__group > h4.md-cheatsheet__group-title { margin-top: 0; } @@ -1414,20 +1316,16 @@ html[data-fcm-mode="essentials"] .grid.cards > ul > li:has(> p[data-fcm-hide~="e full-contrast body ink: lightened toward white on the cream light scheme, dimmed toward black on the dark scheme (which otherwise inherits full --pt-ink from the rule below). */ -[data-md-color-scheme="default"] .md-typeset .pt-category > h4.pt-homepage-heading { +[data-md-color-scheme="default"] .md-typeset .md-cheatsheet__group > h4.md-cheatsheet__group-title { color: color-mix(in srgb, var(--pt-ink) 80%, white); } /* "Add-On Libraries" heading — swap the default h1 margins (0 top / 2.5rem bottom) so the space sits above it instead, separating it from the core grid and pulling it tight to the first library row. */ -.md-typeset .pt-category-grid > h1 { +.md-typeset h1.library-grid-heading { margin-top: 2.5rem; - margin-bottom: 0; -} - -.pt-category--wide { - grid-column: 1 / -1; + margin-bottom: 0.6rem; } .md-typeset .grid.cards > ul > li:hover { @@ -1435,7 +1333,7 @@ html[data-fcm-mode="essentials"] .grid.cards > ul > li:has(> p[data-fcm-hide~="e transform: translateY(-0.1rem); } -.md-typeset .grid.cards > ul > li > p:first-child > .twemoji { +.md-typeset .grid.cards > ul > li > p:first-child > .twemoji:not(.library-badge) { color: var(--pt-accent); } @@ -1449,7 +1347,7 @@ html[data-fcm-mode="essentials"] .grid.cards > ul > li:has(> p[data-fcm-hide~="e border-radius: 0.1rem; } -.md-typeset .grid.cards > ul > li > p:first-child > a:not(.pt-lib-badge)::after { +.md-typeset .grid.cards > ul > li > p:first-child > a::after { content: ""; position: absolute; inset: 0; @@ -1461,14 +1359,14 @@ html[data-fcm-mode="essentials"] .grid.cards > ul > li:has(> p[data-fcm-hide~="e z-index: 2; } -.md-typeset .grid.cards > ul > li .pt-lib-badge { +.md-typeset .grid.cards > ul > li .library-badge { position: absolute; top: 0.35rem; right: 0.35rem; z-index: 2; } -.pt-lib-badge svg { +.library-badge svg { width: 1.05rem; height: 1.05rem; } @@ -1476,13 +1374,13 @@ html[data-fcm-mode="essentials"] .grid.cards > ul > li:has(> p[data-fcm-hide~="e /* Built-in/third-party corner badge: halfway between the card's own background color and the muted category-label color, in both light and dark mode. */ -[data-md-color-scheme="default"] .md-typeset .grid.cards a.pt-lib-badge--builtin, -[data-md-color-scheme="default"] .md-typeset .grid.cards a.pt-lib-badge--third-party { +[data-md-color-scheme="default"] .md-typeset .grid.cards .library-badge--builtin, +[data-md-color-scheme="default"] .md-typeset .grid.cards .library-badge--third-party { color: color-mix(in srgb, var(--pt-panel), color-mix(in srgb, var(--pt-ink) 80%, white)); } -[data-md-color-scheme="slate"] .md-typeset .grid.cards a.pt-lib-badge--builtin, -[data-md-color-scheme="slate"] .md-typeset .grid.cards a.pt-lib-badge--third-party { +[data-md-color-scheme="slate"] .md-typeset .grid.cards .library-badge--builtin, +[data-md-color-scheme="slate"] .md-typeset .grid.cards .library-badge--third-party { color: color-mix(in srgb, color-mix(in srgb, var(--pt-panel) 92%, var(--pt-ink)), color-mix(in srgb, var(--pt-ink) 80%, black)); } @@ -1490,52 +1388,52 @@ html[data-fcm-mode="essentials"] .grid.cards > ul > li:has(> p[data-fcm-hide~="e sub-keyword tags. Everything else — the `####` category headings, card icon, card title, description, and the bold lead keyword in each tag row — is the off-white --pt-ink. The light scheme keeps the original green title + tags. */ -[data-md-color-scheme="slate"] .md-typeset .pt-category .pt-homepage-heading { +[data-md-color-scheme="slate"] .md-typeset .md-cheatsheet__group .md-cheatsheet__group-title { color: color-mix(in srgb, var(--pt-ink) 80%, black); } -[data-md-color-scheme="slate"] .md-typeset .pt-category .grid.cards > ul > li > p:first-child > .twemoji { +[data-md-color-scheme="slate"] .md-typeset .md-cheatsheet__group .grid.cards > ul > li > p:first-child > .twemoji:not(.library-badge) { color: var(--pt-ink); } /* card description — a muted slate-teal blue, matched to the green tag color (#68A87F) in saturation/lightness so the two read as a pair. ~4.8:1 on the card, passes AA. */ -[data-md-color-scheme="slate"] .md-typeset .pt-category .grid.cards > ul > li > p:nth-of-type(2) { +[data-md-color-scheme="slate"] .md-typeset .md-cheatsheet__group .grid.cards > ul > li > p:nth-of-type(2) { color: var(--pt-desc-blue); } -[data-md-color-scheme="slate"] .md-typeset .pt-category .grid.cards > ul > li > p:first-child > a:not(.pt-lib-badge), -[data-md-color-scheme="slate"] .md-typeset .pt-category .grid.cards > ul > li > p:first-child > a strong { +[data-md-color-scheme="slate"] .md-typeset .md-cheatsheet__group .grid.cards > ul > li > p:first-child > a, +[data-md-color-scheme="slate"] .md-typeset .md-cheatsheet__group .grid.cards > ul > li > p:first-child > a strong { color: var(--pt-ink); } /* keyword tag rows: the bold lead keyword (links to a ## section) stays white… */ -[data-md-color-scheme="slate"] .md-typeset .pt-category .grid.cards > ul > li > p:not(:first-child) a, -[data-md-color-scheme="slate"] .md-typeset .pt-category .grid.cards > ul > li > p:not(:first-child) a strong, -[data-md-color-scheme="slate"] .md-typeset .pt-category .grid.cards > ul > li > p:not(:first-child) a strong > code { +[data-md-color-scheme="slate"] .md-typeset .md-cheatsheet__group .grid.cards > ul > li > p:not(:first-child) a, +[data-md-color-scheme="slate"] .md-typeset .md-cheatsheet__group .grid.cards > ul > li > p:not(:first-child) a strong, +[data-md-color-scheme="slate"] .md-typeset .md-cheatsheet__group .grid.cards > ul > li > p:not(:first-child) a strong > code { color: var(--pt-ink); } /* …the smaller non-bold sub-keywords (### anchors) are green */ -[data-md-color-scheme="slate"] .md-typeset .pt-category .grid.cards > ul > li > p:not(:first-child) a > code { +[data-md-color-scheme="slate"] .md-typeset .md-cheatsheet__group .grid.cards > ul > li > p:not(:first-child) a > code { color: var(--pt-accent); } /* Light mode: black card titles and bold keyword leads (e.g. "integers"), a deep teal-blue for the descriptions (pairs with the green sub-tags). */ -[data-md-color-scheme="default"] .md-typeset .pt-category .grid.cards > ul > li > p:first-child > a:not(.pt-lib-badge), -[data-md-color-scheme="default"] .md-typeset .pt-category .grid.cards > ul > li > p:first-child > a strong, -[data-md-color-scheme="default"] .md-typeset .pt-category .grid.cards > ul > li > p:not(:first-child) a strong, -[data-md-color-scheme="default"] .md-typeset .pt-category .grid.cards > ul > li > p:not(:first-child) a strong > code { +[data-md-color-scheme="default"] .md-typeset .md-cheatsheet__group .grid.cards > ul > li > p:first-child > a, +[data-md-color-scheme="default"] .md-typeset .md-cheatsheet__group .grid.cards > ul > li > p:first-child > a strong, +[data-md-color-scheme="default"] .md-typeset .md-cheatsheet__group .grid.cards > ul > li > p:not(:first-child) a strong, +[data-md-color-scheme="default"] .md-typeset .md-cheatsheet__group .grid.cards > ul > li > p:not(:first-child) a strong > code { color: #000; } -[data-md-color-scheme="default"] .md-typeset .pt-category-grid > h1 { +[data-md-color-scheme="default"] .md-typeset h1.library-grid-heading { color: var(--pt-ink); } -[data-md-color-scheme="default"] .md-typeset .pt-category .grid.cards > ul > li > p:nth-of-type(2) { +[data-md-color-scheme="default"] .md-typeset .md-cheatsheet__group .grid.cards > ul > li > p:nth-of-type(2) { color: var(--pt-desc-blue); } diff --git a/docs/types/basics.md b/docs/types/basics.md index 2170f5f..173cf63 100644 --- a/docs/types/basics.md +++ b/docs/types/basics.md @@ -1,14 +1,15 @@ --- +cheatsheet_description: Kinds of values, and what you can do with them. description: >- Python's basic data types explained with runnable examples: integers, floats, strings, booleans, and None, plus the operations each one supports. --- -# :material-shape-outline:{ .lg .middle } Basic data types +# :material-shape-outline:{ .lg .middle } Basic data types { cs="isinstance, type" }
    -Every value in Python has a **type**, which determines what operations it supports and how it behaves. +Every value in Python has a **type**, which determines what operations it supports and how it behaves. A basic data type holds a single value, as opposed to a [collection](collections.md) data type, which groups multiple values together. The basic types covered here — `int`, `float`, `str`, `bool`, and `None` — are immutable. @@ -27,7 +28,7 @@ A basic data type holds a single value, as opposed to a [collection](collections ??? tip "Check what type a variable is" `type()` shows the data type - + `isinstance()` checks whether a value is that type. ```python-ref @@ -43,7 +44,7 @@ A basic data type holds a single value, as opposed to a [collection](collections
    -## Integers +## Integers { cs="integers" } An integer a.k.a. "`int`" is a whole number — positive, negative, or zero — with no decimal point. @@ -51,9 +52,9 @@ An integer a.k.a. "`int`" is a whole number — positive, negative, or zero — length = 5 ``` -### Integer operations { data-card-link="skip" } +### Integer operations -#### Arithmetic +#### Arithmetic { cs="+ - * / **" } If both sides are `int` the result will be `int`, except for division. @@ -77,9 +78,9 @@ If both sides are `int` the result will be `int`, except for division. length ** 2 # 25 (length squared) ``` -#### Floor division & modulo +#### Floor division & modulo { cs="// % divmod" } -- **`//` floor division** divides two numbers and keeps only the whole-number part, dropping anything after the decimal — like asking "how many whole groups of 2 fit into 7?" +- **`//` floor division** divides two numbers and keeps only the whole-number part, dropping anything after the decimal — like asking "how many whole groups of 2 fit into 7?" ```python-ref 7 // 2 # 3 (7 split into groups of 2 makes 3 full groups) @@ -97,17 +98,17 @@ If both sides are `int` the result will be `int`, except for division. divmod(7, 2) # (3, 1) — same as (7 // 2, 7 % 2) ``` -#### Apply arithmetic to a variable +#### Apply arithmetic to a variable { cs="+= -= *= /= //= %= **=" } - **Combine** any of these arithmetic operations with `=` for an augmented assignment — it does that calculation on the variable, and updates the value of the variable, doing two things with one operation. Examples: **`+=` `-=` `*=` `/=` `//=` `%=` `**=`**. ```python-ref length = length + 1 # this way works, it's just longer - - length += 1 # same thing, written more concisely + + length += 1 # same thing, written more concisely ``` -#### Absolute value +#### Absolute value { cs="abs" } - **`abs()`** returns a number with its sign dropped — negative becomes positive, positive stays unchanged. @@ -117,7 +118,7 @@ If both sides are `int` the result will be `int`, except for division. abs(-4.5) # 4.5 ``` -#### Convert +#### Convert { cs="int" } - **`int()`** converts a string of digits, or truncates a float toward zero. It cuts off the decimal — it does not round. @@ -127,7 +128,7 @@ If both sides are `int` the result will be `int`, except for division. int(True) # 1 ``` -### Boolean expressions +### Boolean expressions { cs="boolean expressions" } - **`==` `!=` `>` `<` `>=` `<=`** compare two numbers — see [comparisons by type](#booleans) for the full rundown. @@ -152,10 +153,10 @@ while length: # loops until length reaches 0 length -= 1 ``` -### Going further { data-card-link="skip" } +### Going further ??? run "Practice with integers" - + ```python length = 5 @@ -191,7 +192,7 @@ while length: # loops until length reaches 0
    -## Floats +## Floats { cs="floats" } A float is a number with a decimal point — for anything that isn't a whole number. @@ -199,9 +200,9 @@ A float is a number with a decimal point — for anything that isn't a whole num weight = 4.5 ``` -### Float operations { data-card-link="skip" } +### Float operations -#### Arithmetic +#### Arithmetic { cs="+ - * / **" } If either side of arithmetic is `float` the result will be `float`, except for division. @@ -226,9 +227,9 @@ If either side of arithmetic is `float` the result will be `float`, except for d ``` -#### Floor division & modulo +#### Floor division & modulo { cs="// % divmod" } -- **floor division `//`** divides two numbers and keeps only the whole-number part, dropping anything after the decimal — like asking "how many whole groups of 2 fit into 7?" +- **floor division `//`** divides two numbers and keeps only the whole-number part, dropping anything after the decimal — like asking "how many whole groups of 2 fit into 7?" ```python-ref 7.0 // 2 # 3.0 (7.0 split into groups of 2 makes 3 full groups) @@ -246,17 +247,17 @@ If either side of arithmetic is `float` the result will be `float`, except for d divmod(7.0, 2.0) # (3.0, 1.0) — same as (7.0 // 2.0, 7.0 % 2.0) ``` -#### Apply arithmetic to a variable +#### Apply arithmetic to a variable { cs="+= -= *= /= //= %= **=" } - **Combine** any of these arithmetic operations with `=` for an augmented assignment — it does that calculation on the variable, and updates the value of the variable, doing two things with one operation. Examples: **`+=` `-=` `*=` `/=` `//=` `%=` `**=`**. ```python-ref length = length + 1 # this way works, it's just longer - - length += 1 # same thing, written more concisely + + length += 1 # same thing, written more concisely ``` -#### Adjust +#### Adjust { cs="abs, round" } - **`abs()`** works the same way it does on an `int` — drops the sign, negative becomes positive. @@ -271,7 +272,7 @@ If either side of arithmetic is `float` the result will be `float`, except for d round(4.567, 2) # 4.57 ``` -#### Convert +#### Convert { cs="float" } - **`float()`** converts an integer or a numeric string into a float. @@ -280,7 +281,7 @@ If either side of arithmetic is `float` the result will be `float`, except for d float("4.5") # 4.5 ``` -### Boolean expressions +### Boolean expressions { cs="boolean expressions" } - **`==` `!=` `>` `<` `>=` `<=`** compare two numbers — see [comparisons by type](#booleans) for the full rundown. @@ -302,7 +303,7 @@ if weight: # runs — weight isn't 0.0 print("has a weight") ``` -### Going further { data-card-link="skip" } +### Going further ??? warning "Floating-point precision" Tiny rounding errors creep in, since most decimal fractions can't be stored exactly in binary. @@ -348,7 +349,7 @@ if weight: # runs — weight isn't 0.0
    -## Strings +## Strings { cs="strings" } A string stores text — a sequence of characters — inside a single variable.[^str-collection] @@ -364,9 +365,9 @@ Strings have three defining traits: name = "burmese python" ``` -### String operations { data-card-link="skip" } +### String operations -#### Access characters +#### Access characters { cs="index, slice, step" } Strings use the same index and slice syntax as lists. `0` is the first character, negative indexes count from the end, and `start:end` slices out a substring. @@ -390,7 +391,7 @@ Strings use the same index and slice syntax as lists. `0` is the first character name[::-1] # "nohtyp esemrub" — reversed ``` -#### Inspect +#### Inspect { cs="len" } - **`len()`** returns how many characters are in a string. @@ -398,7 +399,7 @@ Strings use the same index and slice syntax as lists. `0` is the first character len(name) # 15 ``` -#### Combine +#### Combine { cs="+ * += *=, combine, join" } - **`+` joins** strings end to end, building a real string you can store — but every piece must already be text, so joining a string with a number raises a `TypeError` unless you convert the number with `str()` first. @@ -440,8 +441,8 @@ Strings use the same index and slice syntax as lists. `0` is the first character | `+=` 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. - + 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](../practices/style.md#efficiency) for why this distinction matters. @@ -456,9 +457,9 @@ Strings use the same index and slice syntax as lists. `0` is the first character print("b") # "ab" — same line, since end="" skipped the newline ``` -#### Building strings +#### Building strings { cs="f-string, format, format spec" } -- An **f-string** lets you embed variables directly inside `{}` and is a good choice once a string has multiple variables in it. Put a variable's name inside the `{}` and the variable's value will be inserted inside. +- An **f-string** lets you embed variables directly inside `{}` and is a good choice once a string has multiple variables in it. Put a variable's name inside the `{}` and the variable's value will be inserted inside. ```python-ref species = "ball" @@ -515,7 +516,7 @@ Strings use the same index and slice syntax as lists. `0` is the first character print(rf"C:\{species}") # raw and an f-string together ``` -#### Modify +#### Modify { cs="capitalize, lower, replace, strip, title, upper" } Since strings are immutable, these all return a **new** string rather than changing the original. @@ -540,7 +541,7 @@ Since strings are immutable, these all return a **new** string rather than chang name.replace("burmese", "ball") # "ball python" ``` -#### Search +#### Search { cs="count, find, in" } - **`in`** checks whether one string contains another. @@ -560,7 +561,7 @@ Since strings are immutable, these all return a **new** string rather than chang name.count("p") # 1 ``` -#### Validate +#### Validate { cs="endswith, isalpha, isdigit, startswith" } - **`.startswith()`, `.endswith()`** check the beginning or end of a string specifically — faster to read than slicing and comparing manually. @@ -579,7 +580,7 @@ Since strings are immutable, these all return a **new** string rather than chang species.isalpha() # True — every character is a letter ``` -#### Convert +#### Convert { cs="split, str" } - **`str()`** converts almost any value into its text representation. Handy any time you need to combine a number with text, since `+` can't join a string and a number directly. @@ -595,7 +596,7 @@ Since strings are immutable, these all return a **new** string rather than chang name.split() # ["burmese", "python"] ``` -### Boolean expressions +### Boolean expressions { cs="boolean expressions" } - **`==` `!=`** check whether two strings are equal. @@ -627,7 +628,7 @@ if name: # runs — name isn't empty print("has a name") ``` -### Going further { data-card-link="skip" } +### Going further ??? run "Practice with strings" @@ -734,7 +735,7 @@ if name: # runs — name isn't empty
    -## Booleans +## Booleans { cs="booleans" } A boolean (`bool`) holds one of exactly two values, **`True`** or **`False`** — often used to represent yes/no, on/off, or the result of a comparison. They are used in [if statements](../flow/conditionals.md#if-elif-else) and [while loops](../flow/loops.md#while-loops). @@ -742,11 +743,11 @@ A boolean (`bool`) holds one of exactly two values, **`True`** or **`False`** venomous = False ``` -### Boolean expressions +### Boolean expressions { cs="== != > < >= <=, in, is" } - A **boolean expression** is a boolean value (`True` or `False`) or anything that produces one, and is treated as the **condition** that must be `True` in order to run a block of code. -- A comparison looks different depending on the type of value being checked, as shown below. All of these comparisons result in a `True` or `False` boolean expression. +- A comparison looks different depending on the type of value being checked, as shown below. All of these comparisons result in a `True` or `False` boolean expression. !!! example "Comparisons by type" @@ -1037,7 +1038,7 @@ venomous = False constrictors.isdisjoint({"cobra", "viper"}) # True ``` -### Logical operators +### Logical operators { cs="and, not, or" } `not`, `and`, `or` combine booleans (or boolean expressions) to create more complex meanings. @@ -1067,7 +1068,7 @@ venomous = False length > 10 or venomous # True or False → True ``` -- **Order of operations:** When several logical operators appear together, Python evaluates them in the below order. Keeping this in mind, you can use parentheses to help construct your expressions. +- **Order of operations:** When several logical operators appear together, Python evaluates them in the below order. Keeping this in mind, you can use parentheses to help construct your expressions. 1. `not` @@ -1075,7 +1076,7 @@ venomous = False 3. `or` -### Going further { data-card-link="skip" } +### Going further ??? note "Bool is a subclass of int" `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`. @@ -1121,7 +1122,7 @@ venomous = False
    -## None +## None { cs } `None` represents the absence of a value — Python's way of saying "nothing here," distinct from `0`, `False`, or an empty string. @@ -1129,7 +1130,7 @@ venomous = False venomous = None ``` -### Check for None +### Check for None { cs="is, is not" } - **`is`, `is not`** compare against `None` — always use these, not `==`/`!=`. `is` checks that it's the *exact same object*, which is what you want for a singleton value like `None`. @@ -1138,7 +1139,7 @@ venomous = None venomous is not None # False ``` -### Boolean expressions +### Boolean expressions { cs="boolean expressions" } `None` is always falsy — there's no "sometimes truthy" case, since it's the one value that only ever means "nothing here." @@ -1155,7 +1156,7 @@ venomous = None print("nothing found") ``` -### Going further { data-card-link="skip" } +### Going further ??? warning "is vs ==" `is` checks whether two variables point to the *exact same object* in memory, not whether their values are equal — that's what makes it correct for `None` (there's ever only one), but wrong for almost everything else. Small integers and short strings happen to work with `is` too, because Python reuses those specific objects internally, which makes the mistake easy to miss until it silently breaks on a larger number or a value built some other way. diff --git a/docs/types/collections.md b/docs/types/collections.md index 28fa66f..9641ee3 100644 --- a/docs/types/collections.md +++ b/docs/types/collections.md @@ -1,14 +1,15 @@ --- +cheatsheet_description: Multiple related values grouped into one container. description: >- Python's collection types explained with runnable examples: lists, dictionaries, tuples, and sets, plus how to loop through, filter, and combine them. --- -# :material-basket-outline:{ .lg .middle } Collection Data Types +# :material-basket-outline:{ .lg .middle } Collection data types { cs="isinstance, type" }
    -A **collection** is a single object that groups multiple values (like [basic types](basics.md)) together and so they can be stored in one variable together and worked with as a unit. +A **collection** is a single object that groups multiple values (like [basic types](basics.md)) together and so they can be stored in one variable together and worked with as a unit.
    @@ -24,7 +25,7 @@ A **collection** is a single object that groups multiple values (like [basic typ ??? tip "Check what type a variable is" `type()` shows the data type - + `isinstance()` checks whether a value is that type. ```python-ref @@ -40,13 +41,13 @@ A **collection** is a single object that groups multiple values (like [basic typ
    -## Lists +## Lists { cs="lists, item" } -### Create a list +### Create a list { cs="create, index" } -- A list stores multiple items, in order, inside a single variable. The items can be any type. +- A list stores multiple items, in order, inside a single variable. The items can be any type. -- The **index** is the numbered position of an item. The index of the first item is 0[^zero-index], next is 1, and so on. +- The **index** is the numbered position of an item. The index of the first item is 0[^zero-index], next is 1, and so on.
    @@ -73,17 +74,17 @@ class diagram panel
    -- The **negative index** tells you how far from the end it is. It starts counting down from the end instead, starting at `-1` for the last item, -2 for the second-to-last, and so on. +- The **negative index** tells you how far from the end it is. It starts counting down from the end instead, starting at `-1` for the last item, -2 for the second-to-last, and so on. - Each item can be referenced by its positive or negative index. -### Access and update items +### Access and update items { cs="slice, step" } -- **Index with `list[index]`** to return the item at that index (position number) of the list. +- **Index with `list[index]`** to return the item at that index (position number) of the list. To **update** the item at that index, set it equal to something else **`list[index] = new_item`**. This works because a list is **mutable** — updating an item changes it in place instead of building a new one, the same way [an object's attributes](../organization/classes.md#defining-a-class) can be changed after it's created. - *Run the below example, and change the indexes to see how they work:* + *Run the below example, and change the indexes to see how they work:* ```python species = ["burmese", "rock", "ball", "blood"] @@ -91,12 +92,12 @@ class diagram panel print(species[0]) # "burmese" print(species[1]) # "rock" print(species[-1]) # "blood" - + species[1] = "carpet" # update item at index 1 print(species) # ["burmese", "carpet", "ball", "blood"] ``` -- **Access a range of multiple items at once:** +- **Access a range of multiple items at once:** - **Slice with `list[start:end]`** to return a new list containing items from the `start` index up to (but not including) the `end` index. @@ -104,7 +105,7 @@ class diagram panel species[1:3] # ["rock", "ball"], starts at index 1, stops at (doesn't include) index 3 ``` - - **Step with `list[start:end:step]`** to return a new list that can skip items instead of taking every one — `start`/`end` are optional so if you leave them off the step is applied to the whole list. A step of `-1` walks backward, which is the standard trick for reversing a list. You can also add a start and end range just like a slice. + - **Step with `list[start:end:step]`** to return a new list that can skip items instead of taking every one — `start`/`end` are optional so if you leave them off the step is applied to the whole list. A step of `-1` walks backward, which is the standard trick for reversing a list. You can also add a start and end range just like a slice. ```python-ref species[::2] # ["burmese", "ball"] — every 2nd item @@ -112,7 +113,7 @@ class diagram panel ``` ??? tip "Assigning to a range" - Setting a slice or step `=` equal to a list will replaces that whole range at once with the new list. + Setting a slice or step `=` equal to a list will replaces that whole range at once with the new list. A plain **slice** accepts a replacement of *any* length — it doesn't need to match the range being replaced. @@ -126,23 +127,23 @@ class diagram panel species[::2] = ["carpet", "anaconda"] # ["carpet", "rock", "anaconda", "blood"] ``` -### [Loop](../flow/loops.md#loop-through-a-collection) through a list +### [Loop](../flow/loops.md#loop-through-a-collection) through a list { cs="loop" } -- Lists make it simple to loop directly over the items. The loop runs once for every item in the list, and on each pass the new loop variable, *(i.e. `specie`)* is set to the next item in the list. +- Lists make it simple to loop directly over the items. The loop runs once for every item in the list, and on each pass the new loop variable, *(i.e. `specie`)* is set to the next item in the list. ```python - for specie in species: + for specie in species: print(specie) # specie is "burmese", then "rock", then "ball", then "blood" — one item per pass ``` - If you also want the index of the item alongside the item itself, `enumerate()` hands back both together. ```python - for index, specie in enumerate(species): + for index, specie in enumerate(species): print(index, specie) # 0 "burmese", then 1 "rock", then 2 "ball", then 3 "blood" ``` -### Boolean expressions +### Boolean expressions { cs="boolean expressions, in" } - **`in`, `not in`** checks whether a value exists or is missing in the list. @@ -152,7 +153,7 @@ class diagram panel "anaconda" not in species # True ``` -- **`==`, `!=`** checks whether a specific item is equal or not equal to something. +- **`==`, `!=`** checks whether a specific item is equal or not equal to something. ```python-ref species[0] == "burmese" # True @@ -180,8 +181,8 @@ class diagram panel - **`is list empty`** truthiness (boolean value of the whole list) - - Truthy: a list with contents - + - Truthy: a list with contents + - Falsy: an empty list `[]` ```python-ref @@ -192,9 +193,9 @@ class diagram panel species.pop() ``` -### List operations { data-card-link="skip" } +### List operations -#### Inspect +#### Inspect { cs="count, len" } - **`len()`** returns how many items are in a list. @@ -214,7 +215,7 @@ class diagram panel species.count("ball") # 1 ``` -#### Add item +#### Add item { cs="append, extend, insert" } - **`append()`** adds one item to the end of the list. @@ -234,9 +235,9 @@ class diagram panel species.extend(["carpet", "central african rock"]) # ["burmese", "rock", "ball", "blood", "carpet", "central african rock"] ``` -#### Remove item +#### Remove item { cs="clear, del, pop, remove" } -- **`remove()`** deletes the first item that matches a given value. If there are duplicate items, it only removes the first one. +- **`remove()`** deletes the first item that matches a given value. If there are duplicate items, it only removes the first one. ```python-ref species.remove("rock") # ["burmese", "ball", "blood"] @@ -262,7 +263,7 @@ class diagram panel species.clear() # [] ``` -#### Sort +#### Sort { cs="reverse, sort, sorted" } - **`sort()`** sorts the list in place, alphabetically (or ascending, for numbers) by default. Pass `reverse=True` to sort in the opposite order, or a `key` function to control what each item is sorted by. @@ -284,7 +285,7 @@ class diagram panel species.reverse() # ["blood", "ball", "rock", "burmese"] ``` -#### Arithmetic +#### Arithmetic { cs="max, min, sum" } - **`min()`** finds the smallest item. @@ -304,7 +305,7 @@ class diagram panel sum(length_ft) # 25 ``` -#### Create +#### Create { cs="+, copy, list" } - **`list()`** builds the same list from any iterable, if you'd rather not use literal brackets. @@ -325,7 +326,7 @@ class diagram panel backup = species.copy() # backup is a separate, independent list ``` -#### List comprehension +#### List comprehension { cs="comprehension" } - **`[expr for item in iterable]`** builds a new list from an existing iterable in a single line. The expression part can transform each item, not just filter it. @@ -337,7 +338,7 @@ class diagram panel Swapping the brackets for parentheses turns this into a [generator expression](../organization/functions.md#generator-expressions) instead — same syntax, but it produces items one at a time rather than building the whole list up front. Use a list comprehension when the result needs indexing, `len()`, or looping over more than once; use a generator expression when it's only read once, or the full result would be too large to hold in memory as a list. -### Going further { data-card-link="skip" } +### Going further ??? warning "In-place list methods return None" `append()`, `insert()`, `extend()`, `sort()`, `reverse()`, and `remove()` all change the list directly and return `None` — not the changed list. Reassigning the variable to one of their results replaces the list itself with `None`, and the next call on it raises `AttributeError: 'NoneType' object has no attribute '...'`. @@ -439,12 +440,12 @@ class diagram panel ??? tip "Extending lists with `collections.deque`" A list can already add or remove items from the end cheaply, but doing the same at the *front* — `species.insert(0, item)` or `species.pop(0)` — means Python has to shift every - other item over. The [`collections`](../libraries/collections.md) library's - [`deque`](../libraries/collections.md#deque) adds fast `appendleft()`/`popleft()` methods for + other item over. The [`collections`](../libraries/utilities/collections.md) library's + [`deque`](../libraries/utilities/collections.md#deque) adds fast `appendleft()`/`popleft()` methods for exactly that case. Switch to it when items are being added or removed from both ends often, like a queue of items processed in the order they arrive — not for a list that's mostly read or only changed at the end, where a plain list is simpler and already fast. - See the [collections library page](../libraries/collections.md) for the rest of `deque`'s + See the [collections library page](../libraries/utilities/collections.md) for the rest of `deque`'s methods (`rotate()`, `maxlen=`, and more) and for the other list-adjacent tools it adds.
    @@ -465,8 +466,8 @@ class diagram panel | `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. - + `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](../practices/style.md#efficiency) for why this distinction matters. @@ -477,9 +478,9 @@ class diagram panel
    -## Dictionaries +## Dictionaries { cs="dictionaries, key, value" } -### Create a dictionary { data-card-link="skip" } +### Create a dictionary - A dictionary stores data as **key-value pairs**, inside a single variable. Values are looked up by key, not by a numbered position like a list's index — a dict does remember the order keys were added in, but that order isn't how you access anything. @@ -487,8 +488,8 @@ class diagram panel ```python-ref snake = { - "species": "ball", - "length_ft": 5, + "species": "ball", + "length_ft": 5, "venomous": False } ``` @@ -515,7 +516,7 @@ flowchart LR
    -- Each **key** points to exactly one value. +- Each **key** points to exactly one value. - **A key's type** can be a string, int, float, or tuple @@ -523,7 +524,7 @@ flowchart LR - A **value** can be any type. -### Access a value +### Access a value { cs="access a value" } - **`dict[key]`** accesses a value by key, in square brackets. This raises `KeyError` if the key is missing — use it when a missing key means something's wrong and should surface as an error. @@ -552,30 +553,30 @@ flowchart LR
    -### Loop through a dictionary +### Loop through a dictionary { cs="items, loop, values" } - 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. ```python-ref - for key in snake: + for key in snake: print(key) # species length_ft venomous ``` - Loop over **`.values()`** to get just the values instead. ```python-ref - for value in snake.values(): + for value in snake.values(): print(value) # ball 5 False ``` - Loop over **`.items()`** to get both the key and the value together. ```python-ref - for key, value in snake.items(): + for key, value in snake.items(): print(key, value) # species ball length_ft 5 venomous False ``` -### Boolean expressions +### Boolean expressions { cs="boolean expressions" } - **`in`** checks whether a key exists at all. @@ -621,9 +622,9 @@ flowchart LR snake.popitem() ``` -### Dictionary operations +### Dictionary operations { cs="get" } -#### Inspect +#### Inspect { cs="len" } - **`len()`** returns how many key-value pairs are in a dictionary. @@ -631,7 +632,7 @@ flowchart LR len(snake) # 3 ``` -#### Update +#### Update { cs="update" } - **`dict[key] = value`** sets a key's value — changes it if the key already exists, adds it if not. @@ -646,7 +647,7 @@ flowchart LR snake.update({"venomous": False, "docile": True}) # {'species': 'ball', 'length_ft': 5, 'venomous': False, 'docile': True} ``` -#### Remove +#### Remove { cs="clear, del, pop, popitem" } - **`pop()`** removes a key and returns its value. @@ -672,7 +673,7 @@ flowchart LR snake.clear() # {} ``` -#### Create +#### Create { cs="copy, dict" } - **`dict()`** builds the same dictionary using keyword arguments, if you'd rather not use literal braces. @@ -688,15 +689,15 @@ flowchart LR ``` ??? note "Nested dictionaries" - A dictionary's values can be other dictionaries. Useful for grouping related records under one variable, like a whole collection of snakes keyed by species. - + A dictionary's values can be other dictionaries. Useful for grouping related records under one variable, like a whole collection of snakes keyed by species. + Chain operations one after the other to reach a value nested inside an inner dictionary. ```python-ref snakes = { - "ball": snake, + "ball": snake, "burmese": { - "length_ft": 16, + "length_ft": 16, "venomous": False } } @@ -717,7 +718,7 @@ flowchart LR
    -### Going further { data-card-link="skip" } +### Going further ??? run "Practice with dictionaries" Each box below is fully editable — write your answer, then click Run. @@ -798,31 +799,31 @@ flowchart LR ??? tip "Extending dicts with `collections`" A plain dict can tally counts or group items, but both take extra setup code: checking whether a key exists before incrementing it, or before appending to a list under it. The - [`collections`](../libraries/collections.md) library adds several dicts that handle cases + [`collections`](../libraries/utilities/collections.md) library adds several dicts that handle cases like these automatically. - - [`Counter`](../libraries/collections.md#counter) counts items in a sequence directly — + - [`Counter`](../libraries/utilities/collections.md#counter) counts items in a sequence directly — reach for it as soon as a dict's job is "how many times does each item show up." - - [`defaultdict`](../libraries/collections.md#defaultdict) supplies an empty value (a list, + - [`defaultdict`](../libraries/utilities/collections.md#defaultdict) supplies an empty value (a list, a set, `0`) the first time a new key is used, so grouping items under keys that aren't known ahead of time doesn't need an `if key not in dict` check before every write. - - [`OrderedDict`](../libraries/collections.md#ordereddict) is worth reaching for only when + - [`OrderedDict`](../libraries/utilities/collections.md#ordereddict) is worth reaching for only when order itself needs to be compared or reordered — a plain dict already remembers insertion order, but its `==` ignores that order, and it has no `move_to_end()`. - - [`ChainMap`](../libraries/collections.md#chainmap) layers several dicts together — like a + - [`ChainMap`](../libraries/utilities/collections.md#chainmap) layers several dicts together — like a set of overrides checked before a set of defaults — without copying or merging them into a new dict. - See the [collections library page](../libraries/collections.md) for the full method list on + See the [collections library page](../libraries/utilities/collections.md) for the full method list on each of these.
    -## Tuples { data-fcm-hide="essentials" } +## Tuples { data-fcm-hide="essentials" cs="tuples, immutable, index" } -A tuple stores multiple items, in order, written in parentheses. They are **immutable** so the items can't be changed once its created. +A tuple stores multiple items, in order, written in parentheses. They are **immutable** so the items can't be changed once its created.
    @@ -849,11 +850,11 @@ block-beta
    -The **index** of the first item is 0[^zero-index], next is 1, and so on. +The **index** of the first item is 0[^zero-index], next is 1, and so on. The **negative index** starts counting down from the end instead, starting at `-1` for the last item, -2 for the second-to-last, and so on. Each item can be referenced by its positive or negative index. -### Access items +### Access items { cs="access items" } - Index with `tuple[index]`. @@ -868,23 +869,23 @@ The **negative index** starts counting down from the end instead, starting at `- species[1:3] # ("rock", "ball") ``` -### Loop through a tuple +### Loop through a tuple { cs="loop" } - The [loop](../flow/loops.md#loop-through-a-collection) runs once for every item in the tuple, and on each pass the loop variable, *(i.e. `specie`)* is set to the next item in the tuple. ```python-ref - for specie in species: + for specie in species: print(specie) # burmese rock ball blood ``` - If you also want the index alongside the item, `enumerate()` hands back both together — works the same as on a list, since tuples support indexing too. ```python-ref - for index, specie in enumerate(species): + for index, specie in enumerate(species): print(index, specie) # 0 "burmese", then 1 "rock", then 2 "ball", then 3 "blood" ``` -### Boolean expressions +### Boolean expressions { cs="boolean expressions" } - **`in`** checks whether a value exists in the tuple. @@ -928,7 +929,7 @@ The **negative index** starts counting down from the end instead, starting at `- ``` -### Packing and unpacking +### Packing and unpacking { cs="packing, unpacking" } - **Packing:** writing several values separated by commas, with or without the surrounding parentheses, implicitly builds a tuple. @@ -965,9 +966,9 @@ The **negative index** starts counting down from the end instead, starting at `- a, b = b, a # swaps directly — no temporary variable needed — a="boa" b="ball python" ``` -### Tuple operations { data-card-link="skip" } +### Tuple operations -#### Inspect +#### Inspect { cs="count, index, len" } - **`len()`** returns how many items are in a tuple. @@ -987,7 +988,7 @@ The **negative index** starts counting down from the end instead, starting at `- species.index("ball") # 2 ``` -#### Arithmetic +#### Arithmetic { cs="max, min, sum" } - **`min()`** finds the smallest item. @@ -1018,7 +1019,7 @@ The **negative index** starts counting down from the end instead, starting at `- species = tuple(species_list) # convert back to tuple and reassign ``` -#### Create +#### Create { cs="tuple" } - **`tuple()`** builds the same tuple from any iterable, if you'd rather not use literal parentheses. @@ -1026,7 +1027,7 @@ The **negative index** starts counting down from the end instead, starting at `- tuple(["burmese", "rock", "ball", "blood"]) # ("burmese", "rock", "ball", "blood") ``` -### Going further { data-card-link="skip" } +### Going further ??? run "Practice with tuples" Each box below is fully editable — write your answer, then click Run. @@ -1080,20 +1081,20 @@ The **negative index** starts counting down from the end instead, starting at `- ??? tip "Extending tuples with `collections.namedtuple`" A plain tuple's items can only be accessed by position — `snake[1]` doesn't say what that value actually means without checking back how the tuple was built. The - [`collections`](../libraries/collections.md) library's - [`namedtuple`](../libraries/collections.md#namedtuple) builds a tuple type with named fields, + [`collections`](../libraries/utilities/collections.md) library's + [`namedtuple`](../libraries/utilities/collections.md#namedtuple) builds a tuple type with named fields, so the same value reads as `snake.length_ft`. Switch to it once a tuple's positions start needing a mental lookup table to remember, or once several tuples share the same shape throughout a program — a single `namedtuple` definition documents that shape once instead of repeating a comment at every literal. - See the [collections library page](../libraries/collections.md) for `namedtuple`'s other + See the [collections library page](../libraries/utilities/collections.md) for `namedtuple`'s other methods (`_asdict()`, `_replace()`, default field values) and the rest of the module.
    -## Sets { data-fcm-hide="essentials" } +## Sets { data-fcm-hide="essentials" cs="sets" } A set stores multiple items, in no particular order, inside a single variable — written with curly braces. @@ -1122,16 +1123,16 @@ block-beta
    -### Loop through a set +### Loop through a set { cs="loop" } The [loop](../flow/loops.md#loop-through-a-collection) runs once for every item in the set, in no guaranteed order, and on each pass the loop variable, *(i.e. `specie`)* is set to the next item. ```python-ref -for specie in species: +for specie in species: print(specie) # burmese rock ball blood — order not guaranteed ``` -### Boolean expressions +### Boolean expressions { cs="boolean expressions" } - **`in`** checks whether a value exists — and does it far faster than a list or tuple, no matter how large the set gets, since Python looks it up directly instead of scanning item by item. @@ -1179,9 +1180,9 @@ for specie in species: species.pop() ``` -### Set operations { data-card-link="skip" } +### Set operations -#### Inspect +#### Inspect { cs="len" } - **`len()`** returns how many items are in a set. @@ -1189,7 +1190,7 @@ for specie in species: len(species) # 4 ``` -#### Arithmetic +#### Arithmetic { cs="max, min, sum" } - **`min()`** finds the smallest item. @@ -1210,7 +1211,7 @@ for specie in species: sum(length_ft) # 25 ``` -#### Update +#### Update { cs="add, update" } - **`add()`** adds a single item. Adding a value that's already present changes nothing. @@ -1225,7 +1226,7 @@ for specie in species: species.update(["carpet", "boa"]) # adds "boa"; "carpet" was already there ``` -#### Remove +#### Remove { cs="clear, discard, pop, remove" } - **`remove()`** deletes an item, raising an error if it isn't there. @@ -1251,7 +1252,7 @@ for specie in species: species.clear() # set() ``` -#### Combine +#### Combine { cs="| & - ^" } Sets support the same operations as sets in math class — useful for comparing two groups directly instead of writing your own loop to do it. @@ -1284,7 +1285,7 @@ pet_friendly = {"ball", "burmese", "corn snake"} constrictors ^ pet_friendly # {"boa", "corn snake"} ``` -#### Compare +#### Compare { cs="isdisjoint, issubset, issuperset" } These check a relationship between two sets and hand back a `bool`, rather than building a new set the way [Combine](#combine) does. @@ -1306,7 +1307,7 @@ These check a relationship between two sets and hand back a `bool`, rather than constrictors.isdisjoint({"cobra", "viper"}) # True ``` -#### Create +#### Create { cs="copy, set" } - **`set()`** builds the same set from any iterable, if you'd rather not use literal braces. @@ -1343,7 +1344,7 @@ These check a relationship between two sets and hand back a `bool`, rather than
    -### Going further { data-card-link="skip" } +### Going further ??? run "Practice with sets" Each box below is fully editable — write your answer, then click Run. diff --git a/mkdocs.yml b/mkdocs.yml index f8e476a..ca972ea 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -19,6 +19,12 @@ plugins: # marking convention. "Advanced" is listed first and marked default: true # so it keeps showing everything on first visit, matching this site's # prior behavior. + # Homepage and libraries/index.md card grids, built from {cs} heading + # flags on each page — see STRUCTURE.md's "Cheatsheet flags" section. + - cheatsheet: + button: true + sort: alphabetical + group_heading_level: 4 - audience_toggle: aria_label: Content level # theme_toggle.js creates #pt-theme-toggle before this plugin's script runs. @@ -72,32 +78,32 @@ nav: - Libraries: - All: libraries/index.md - Utilities: - - collections: libraries/collections.md - - datetime: libraries/datetime.md - - math: libraries/math.md - - random: libraries/random.md - - re: libraries/re.md - - time: libraries/time.md + - collections: libraries/utilities/collections.md + - datetime: libraries/utilities/datetime.md + - math: libraries/utilities/math.md + - random: libraries/utilities/random.md + - re: libraries/utilities/re.md + - time: libraries/utilities/time.md - Data analysis: - - csv: libraries/csv.md - - matplotlib: libraries/matplotlib.md - - NumPy: libraries/numpy.md - - pandas: libraries/pandas.md + - csv: libraries/data_analysis/csv.md + - matplotlib: libraries/data_analysis/matplotlib.md + - NumPy: libraries/data_analysis/numpy.md + - pandas: libraries/data_analysis/pandas.md - APIs: - - json: libraries/json.md - - requests: libraries/requests.md - - Web scraping: - - BeautifulSoup: libraries/beautifulsoup.md - - Image editing: - - Pillow: libraries/pillow.md + - json: libraries/apis/json.md + - requests: libraries/apis/requests.md + - Web: + - BeautifulSoup: libraries/web/beautifulsoup.md + - Images: + - Pillow: libraries/images/pillow.md - Computer vision: - - OpenCV: libraries/opencv.md + - OpenCV: libraries/computer_vision/opencv.md - Desktop UIs: - - Tkinter: libraries/tkinter.md + - Tkinter: libraries/desktop_uis/tkinter.md - Games: - - turtle: libraries/turtle.md + - turtle: libraries/games/turtle.md - Testing: - - pytest: libraries/pytest.md + - pytest: libraries/testing/pytest.md theme: name: material @@ -190,7 +196,6 @@ extra_javascript: - javascripts/copyright_year.js - javascripts/external_links.js - javascripts/homepage_header_title.js - - javascripts/cheatsheet_button.js - javascripts/theme_toggle.js - javascripts/a11y_patches.js - https://unpkg.com/mermaid@11/dist/mermaid.min.js diff --git a/requirements.txt b/requirements.txt index b242836..18b02d5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,6 +6,9 @@ mkdocs-nested-tabs==0.2.0 # Extracted Essentials/Advanced content toggle — see CLAUDE.md's "Planned # extraction" section. Published to PyPI; was a local editable install before 2026-09-26. mkdocs-audience-toggle==0.1.0 +# Extracted homepage/libraries cheatsheet grid — see CLAUDE.md's "Homepage" +# section. Published to PyPI 2026-09-27. +mkdocs-cheatsheet==0.1.0 pytest playwright pytest-playwright diff --git a/tests/test_accessibility_browser.py b/tests/test_accessibility_browser.py index a0c4da0..3740aeb 100644 --- a/tests/test_accessibility_browser.py +++ b/tests/test_accessibility_browser.py @@ -20,7 +20,7 @@ # FAQ tabs), a content page with a wide comparison table, a page built from numbered # walkthroughs and tabbed OS instructions, one dense with admonitions, and a library page # full of images. -PAGES = ["/", "/types/basics/", "/start/workspace/", "/types/collections/", "/libraries/pillow/"] +PAGES = ["/", "/types/basics/", "/start/workspace/", "/types/collections/", "/libraries/images/pillow/"] # The pages whose palette does the most work — card grid, wide truth tables — re-checked # with the dark scheme active. (Material lists `slate` first, so dark is already the diff --git a/tests/test_content_mode_toggle.py b/tests/test_content_mode_toggle.py index e789ae8..354d483 100644 --- a/tests/test_content_mode_toggle.py +++ b/tests/test_content_mode_toggle.py @@ -106,23 +106,18 @@ def test_pfg_section_wrapper_is_hidden_with_its_heading(page, site_url): assert result["wrapperDisplay"] == "none", "the wrapper is still rendering as an empty card" -def test_whole_homepage_card_hides_with_its_first_paragraph(page, site_url): - """Regression: attr_list can only attach data-fcm-hide to a card's first paragraph - (the icon/title line), not the surrounding
  • — python-markdown's attr_list - can't target a list item with more than one paragraph. extra.css hides the - whole card with a :has() rule keyed off that same marker plus the plugin's own - html[data-fcm-mode] — see the "mkdocs-audience-toggle plugin hides..." - comment in extra.css.""" +def test_whole_homepage_card_hides(page, site_url): + """A page with `cheatsheet_attrs: {data-fcm-hide: essentials}` in its front matter + gets the marker on its cheatsheet card's
  • , which the audience toggle hides.""" page.goto(f"{site_url}/?mode=essentials") card_display = page.evaluate( """() => { - const marked = document.querySelector('.grid.cards > ul > li > p[data-fcm-hide~="essentials"]'); - const card = marked ? marked.closest('li') : null; + const card = document.querySelector('.md-cheatsheet__card[data-fcm-hide~="essentials"]'); return card ? getComputedStyle(card).display : null; }""" ) - assert card_display == "none", "a card marked via its first paragraph should fully hide" + assert card_display == "none", "a card marked via cheatsheet_attrs should fully hide" def test_link_to_hidden_section_recovers_to_advanced(page, site_url): diff --git a/tests/test_structure.py b/tests/test_structure.py index 38c1ef1..55fbadf 100644 --- a/tests/test_structure.py +++ b/tests/test_structure.py @@ -230,7 +230,7 @@ def test_python_ref_teaser_lines_have_output_comments(path): # ============================================================================ -# mkdocs build has no WARNINGs (STRUCTURE.md "Link maintenance" / "Homepage keyword deep-links") +# mkdocs build has no WARNINGs (STRUCTURE.md "Link maintenance") # ============================================================================ @@ -243,83 +243,3 @@ def test_mkdocs_build_has_no_warnings(built_site): "mkdocs build printed warnings — STRUCTURE.md treats these as a checklist, not just " "informational output:\n" + "\n".join(warning_lines) ) - - -# ============================================================================ -# index.md's keyword deep-links cover every ##/### heading (STRUCTURE.md "Homepage -# keyword deep-links") -# ============================================================================ - -# A heading opts out of this check from the source markdown itself, via attr_list (already -# enabled — see mkdocs.yml — and already used elsewhere for exactly this kind of per-element -# metadata, e.g. `{ .pt-homepage-heading }`): -# -# ## Heading text { data-card-link="skip" } -# — this heading is narrative/descriptive, not a reusable keyword (STRUCTURE.md's own -# examples: "What do you see when a program runs?"). No index.md entry required. -# -# ## Heading text (no attribute — the default) -# — index.md must link to this heading's anchor. Text is never checked — renaming an -# entry, or the heading, is a manual concern, not this test's. -# -# attr_list attributes land directly on the rendered heading tag, so this reads straight off -# the built HTML alongside id/text — no separate markdown-source parsing needed. -PAGES_SKIPPED_FOR_COVERAGE = {"index.md", "404.md", "about.md", "privacy.md", "thanks.md", "libraries/index.md"} - -LINK_RE = re.compile(r"\]\(([\w./-]+\.md)(#[\w-]+)?\)") -BUILT_HEADING_RE = re.compile(r']*)>(.*?)', re.S) -ATTR_VALUE_RE = re.compile(r'(\w[\w-]*)="([^"]*)"') -TAG_RE = re.compile(r"<[^>]+>") - - -def _index_links(index_md_rel: str): - """page.md -> set of anchors linked from the given index page.""" - index_text = (DOCS_DIR / index_md_rel).read_text() - linked: dict[str, set[str]] = {} - for page, anchor in LINK_RE.findall(index_text): - if anchor: - linked.setdefault(page, set()).add(anchor[1:]) - return linked - - -def _html_path_for(built_site, md_rel: str): - site_dir = built_site["site_dir"] - if md_rel == "index.md": - return site_dir / "index.html" - return site_dir / md_rel[: -len(".md")] / "index.html" - - -@pytest.mark.parametrize("path", ALL_DOC_FILES, ids=DOC_IDS) -def test_homepage_keyword_links_cover_all_headings(built_site, path): - md_rel = rel(path) - if md_rel in PAGES_SKIPPED_FOR_COVERAGE: - pytest.skip("not a content page covered by the homepage card grid") - - html_file = _html_path_for(built_site, md_rel) - assert html_file.exists(), f"no build output for {md_rel} at {html_file}" - - if md_rel.startswith("libraries/"): - index_md_rel = "libraries/index.md" - lookup_key = md_rel[len("libraries/") :] - else: - index_md_rel = "index.md" - lookup_key = md_rel - - linked = _index_links(index_md_rel).get(lookup_key, set()) - failures = [] - for level, attrs_raw, text in BUILT_HEADING_RE.findall(html_file.read_text()): - attrs = dict(ATTR_VALUE_RE.findall(attrs_raw)) - anchor_id = attrs.get("id") - if not anchor_id or attrs.get("data-card-link") == "skip": - continue - if anchor_id not in linked: - clean_text = TAG_RE.sub("", text).strip() - failures.append( - f"{md_rel}#{anchor_id} (h{level} {clean_text!r}) has no {index_md_rel} keyword link" - ) - assert not failures, ( - f"Every ##/### heading needs its own {index_md_rel} keyword deep-link (STRUCTURE.md " - "'Homepage keyword deep-links: Coverage'). Add the link, or mark the heading " - '`{ data-card-link="skip" }` if it\'s intentionally not a reusable keyword:\n' - + "\n".join(failures) - )