From c291618e3a592ac2b3c348437f4ca05e0420fa47 Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Tue, 15 Sep 2026 09:53:31 -0700 Subject: [PATCH 01/12] minor edits --- docs/about.md | 2 +- docs/errors.md | 2 +- docs/oop.md | 15 +++++++++++---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/about.md b/docs/about.md index 31d08b2..23afeca 100644 --- a/docs/about.md +++ b/docs/about.md @@ -8,7 +8,7 @@ description: Why Python Field Guide exists, who built it, and how to send feedba ## What is Python? -Python is a general-purpose language built for code that's easy to read back later, even by someone who didn't write it. There's no compiling: write a `.py` file, run it directly. +Python is a general-purpose language built for code that's easy to read back later, even by someone who didn't write it. No separate compilation step: write a .py file and run it with Python. Python handles the work of preparing and executing your code for you. It shows up everywhere — web backends, data analysis and machine learning, automating repetitive tasks, scientific computing, quick glue scripts. Several of these are covered on this site's [Libraries](index.md#utilities) pages. diff --git a/docs/errors.md b/docs/errors.md index 226cc98..b856044 100644 --- a/docs/errors.md +++ b/docs/errors.md @@ -148,7 +148,7 @@ NameError: name 'name' is not defined The **last line** and the **file and line** above it still matter most, same as before. -**Longer tracebacks** show one `File` line per function call involved — your code calling a function, which calls another function, and so on. Keep reading bottom to top: the first `File` line naming *your own file* (not a library you imported) is almost always the one worth looking at — the frames above it are usually just the library code that was doing what your code asked, not the actual source of the bug. +**Longer tracebacks** show one `File` line per function call involved — your code calling a function, which calls another function, and so on. Start at the bottom of the traceback to identify the exception. Then read upward through the stack to understand how your program got there, looking first at the lines in your own code. ```python-ref Traceback (most recent call last): diff --git a/docs/oop.md b/docs/oop.md index f6c0f08..31c8f27 100644 --- a/docs/oop.md +++ b/docs/oop.md @@ -133,12 +133,19 @@ ball.describe() # "a 5 ft ball python" ``` ??? tip "Modify & delete attributes" - Assign to `object.attribute` to change it after creation. `del object.attribute` removes a single attribute; `del object` removes the object itself. + Assign to `object.attribute` to change it after creation — an object is **mutable**, so this changes it in place, the same as [updating an item in a list](collections.md#access-and-update-items). That also means a second variable pointing at the same object sees the change too: `twin = ball` doesn't copy `ball`, it just gives the same object a second name. + + `del object.attribute` removes a single attribute; `del object` removes the object itself. ```python-ref - ball.length_ft = 6 # change an attribute directly, like any variable - del ball.length_ft # remove just that attribute - del ball # remove the whole object + ball.length_ft = 6 # change an attribute directly, like any variable + + twin = ball # twin and ball are the same object, not a copy + twin.length_ft = 7 # mutates that shared object + print(ball.length_ft) # 7 — the change shows up through ball too + + del ball.length_ft # remove just that attribute + del ball # remove the whole object ``` ??? tip "pass placeholder" From c1f6deac50b8f81bf803a67a180d6387e4f77424 Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Tue, 15 Sep 2026 09:54:52 -0700 Subject: [PATCH 02/12] add mutable column to collections --- docs/collections.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/collections.md b/docs/collections.md index 87e7d61..4859bb3 100644 --- a/docs/collections.md +++ b/docs/collections.md @@ -10,12 +10,12 @@ A **collection** is a single object that groups multiple values (like [basic typ
-| Collection Type | Example | Access values by | Use it for | -|------|---------|:-----------------:|------------| -| **`list`** |
["ball", "burmese"]
| position # |
  • An ordered group of items you can freely add to, remove from, or reorder
  • Not sure? Start here — the default, general-purpose choice
| -| **`dictionary "dict"`** |
{
  "species": "ball",
  "length_ft": 5
}
| Name of a key |
  • Values stored under names ("keys") instead of position, like `species`, `length_ft`
  • Use it to look values up by name
  • Can't have duplicate keys
| -| **`tuple`** |
("ball", "burmese")
| position # |
  • Like a list, but fixed — can't be changed once created
  • Values that should stay exactly as they are, like a coordinate pair
| -| **`set`** |
{"ball", "burmese"}
| Membership (`in`) |
  • An unordered group where duplicates are automatically dropped
  • Use it for fast "is this in here?" checks
| +| Collection Type | Example | Access values by | Mutable | Use it for | +|------|---------|:-----------------:|:-------:|------------| +| **`list`** |
["ball", "burmese"]
| position # | :material-check:{ .pt-icon-success } |
  • An ordered group of items you can freely add to, remove from, or reorder
  • Not sure? Start here — the default, general-purpose choice
| +| **`dictionary "dict"`** |
{
  "species": "ball",
  "length_ft": 5
}
| Name of a key | :material-check:{ .pt-icon-success } |
  • Values stored under names ("keys") instead of position, like `species`, `length_ft`
  • Use it to look values up by name
  • Can't have duplicate keys
| +| **`tuple`** |
("ball", "burmese")
| position # | :material-close:{ .pt-icon-fail } |
  • Like a list, but fixed — can't be changed once created
  • Values that should stay exactly as they are, like a coordinate pair
| +| **`set`** |
{"ball", "burmese"}
| Membership (`in`) | :material-check:{ .pt-icon-success } |
  • An unordered group where duplicates are automatically dropped
  • Use it for fast "is this in here?" checks
|
@@ -77,7 +77,7 @@ class diagram panel - **Index with `list[index]`** to return the item at that index (position number) of the list. - To **update** the item at that index, set it equal to something else **`list[index] = new_item`**. + 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](oop.md#classes-and-objects) can be changed after it's created. *Run the below example, and change the indexes to see how they work:* From 1ad816fecd29b1e7729695b2d23553bbaa710980 Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Tue, 15 Sep 2026 09:55:26 -0700 Subject: [PATCH 03/12] add expressions and statements section --- docs/foundations.md | 40 ++++++++++++++++++++++++++++++++++++++++ docs/index.md | 2 ++ 2 files changed, 42 insertions(+) diff --git a/docs/foundations.md b/docs/foundations.md index f3a6aab..8582f64 100644 --- a/docs/foundations.md +++ b/docs/foundations.md @@ -347,6 +347,46 @@ Other languages fix a variable to one type permanently at creation; Python doesn
+## Expressions and statements + +Every line of Python code is either an **expression** or a **statement**. An expression is anything that evaluates to a value — `2 + 3`, `species`, `species == "burmese"`. A statement is a complete instruction — an assignment, a `print()` call, an `if` statement's condition (covered on the [Conditionals](conditionals.md#if-elif-else) page) — and it's usually built out of one or more expressions. + +```python-ref +2 + 3 # an expression — evaluates to 5 +species # an expression — evaluates to whatever species currently holds +species == "burmese" # an expression — evaluates to True or False +length_ft = 2 + 3 # a statement — the expression "2 + 3" evaluates first, then gets stored +print(length_ft) # a statement — print() evaluates the expression "length_ft" to display it +``` + +The distinction is about what's allowed where: an expression can go anywhere Python expects a value — inside a function call's parentheses, on the right side of `=`, as part of a longer expression — but a statement can't, since it doesn't evaluate to anything. + +??? warning "A statement can't be nested inside other code" + `y = (x = 2 + 3)` raises a `SyntaxError` — `=` only works as its own standalone statement, so tucking one inside another line, even in parentheses, fails immediately. This is different from some other languages, where assignment can be chained or nested like a value. + + ```python-ref + x = 2 + 3 # fine — a standalone statement + y = (x = 2 + 3) # SyntaxError — a statement can't sit inside an expression + ``` + +??? run "Run an expressions vs. statements example" + All the examples above, combined into one script: + + ```python + species = "burmese" + + print(2 + 3) + print(species) + print(species == "burmese") + + length_ft = 2 + 3 + print(length_ft) + ``` + +
+ +
+ ## Input function `input()` allows the program to get typed input from the user diff --git a/docs/index.md b/docs/index.md index f97fba7..2207c79 100644 --- a/docs/index.md +++ b/docs/index.md @@ -130,6 +130,8 @@ hide: [`reassigning`](foundations.md#reassigning-a-variable) [`types`](foundations.md#variables-and-types) + [**`expressions and statements`**](foundations.md#expressions-and-statements) + [**`print`**](foundations.md#print-function): [`escape sequences`](foundations.md#escape-sequences) From 11b736a3dbac40d8cc99ddecaf2d6a15120351ba Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Tue, 15 Sep 2026 09:56:43 -0700 Subject: [PATCH 04/12] mini math and re pages --- docs/index.md | 6 + docs/libraries/index.md | 90 ++++++++++---- docs/libraries/math.md | 258 ++++++++++++++++++++++++++++++++++++++++ docs/libraries/re.md | 203 +++++++++++++++++++++++++++++++ mkdocs.yml | 2 + 5 files changed, 534 insertions(+), 25 deletions(-) create mode 100644 docs/libraries/math.md create mode 100644 docs/libraries/re.md diff --git a/docs/index.md b/docs/index.md index 2207c79..470e0ff 100644 --- a/docs/index.md +++ b/docs/index.md @@ -633,10 +633,13 @@ hide: [`creating a specific date`](libraries/datetime.md#creating-a-specific-date) [`date`](libraries/datetime.md#creating-dates-and-times) [`strftime`](libraries/datetime.md#formatting-with-strftime) +- :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" } [`difference between two dates`](libraries/datetime.md#difference-between-two-dates) [`strptime`](libraries/datetime.md#parsing-a-string-with-strptime) [`timedelta`](libraries/datetime.md#date-arithmetic) + 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" } @@ -644,10 +647,13 @@ hide: Random numbers, random picks, shuffled order. [**`randint`**](libraries/random.md#random-numbers) +- :material-regex:{ .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" } [**`choice`**](libraries/random.md#random-selections): [`sample`](libraries/random.md#sampling-without-replacement) [`shuffle`](libraries/random.md#shuffling-a-list) + Regular expressions: searching, extracting, and replacing text by pattern.
diff --git a/docs/libraries/index.md b/docs/libraries/index.md index 9383c3a..547bf95 100644 --- a/docs/libraries/index.md +++ b/docs/libraries/index.md @@ -11,31 +11,7 @@ Libraries allow us to apply Python to real tasks. These are a few popular ones,
-
-#### 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) - -
-
- -
+
#### Utilities { .pt-homepage-heading }
@@ -112,6 +88,29 @@ Libraries allow us to apply Python to real tasks. These are a few popular ones, [`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" } @@ -123,6 +122,23 @@ Libraries allow us to apply Python to real tasks. These are a few popular ones, [`sample`](random.md#sampling-without-replacement) [`shuffle`](random.md#shuffling-a-list) +- :material-regex:{ .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) +
@@ -315,6 +331,30 @@ Libraries allow us to apply Python to real tasks. These are a few popular ones,
+
+#### 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 } diff --git a/docs/libraries/math.md b/docs/libraries/math.md new file mode 100644 index 0000000..010b2cd --- /dev/null +++ b/docs/libraries/math.md @@ -0,0 +1,258 @@ +--- +description: >- + Rounding, roots, constants, and logarithms in Python with the math module, with runnable + examples. +--- + +# :material-square-root-box:{ .lg .middle } math library + +[Official documentation :material-open-in-new:](https://docs.python.org/3/library/math.html){ target="_blank" } + +The **`math`** module extends Python's built-in arithmetic with functions it doesn't provide directly — square roots, rounding modes, constants like pi, and logarithms. + +
+ +## Install { data-card-link="skip" } + +`math` ships with Python's standard library — nothing to install. + +
+ +
+ +## Import { data-card-link="skip" } + +The whole module is used through the `math.` prefix, so a plain import is all you need. + +```python-ref +import math +``` + +| Function/constant | Returns | Example | +|--------------------|---------|---------| +| `floor(x)` | Largest integer `<= x` | `floor(6.75)` → `6` | +| `ceil(x)` | Smallest integer `>= x` | `ceil(6.75)` → `7` | +| `sqrt(x)` | Square root of `x` | `sqrt(16)` → `4.0` | +| `pow(x, y)` | `x` raised to `y`, as a float | `pow(4, 2)` → `16.0` | +| `pi` | The constant π | `3.141592653589793` | +| `log(x)`, `log2(x)`, `log10(x)` | Logarithm of `x`, in the given base | `log2(64)` → `6.0` | +| `isclose(a, b)` | Whether `a` and `b` are close enough to count as equal | `isclose(0.1 + 0.2, 0.3)` → `True` | + +
+ +
+ +## Rounding + +`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. + +```python-ref +import math + +lengths_ft = [3.5, 12, 4.75] +avg = sum(lengths_ft) / len(lengths_ft) + +print(avg) +print(math.floor(avg)) +print(math.ceil(avg)) +``` + +### trunc + +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. + +```python-ref +math.trunc(6.75) # 6 — same as floor here +math.trunc(-6.75) # -6 — floor(-6.75) would be -7 +``` + +??? run "Run a rounding example" + All the examples above, combined into one script: + + ```python + import math + + lengths_ft = [3.5, 12, 4.75] + avg = sum(lengths_ft) / len(lengths_ft) + + print(avg) + print(math.floor(avg)) + print(math.ceil(avg)) + + import math + + print(math.trunc(6.75)) + print(math.trunc(-6.75)) + ``` + +
+ +
+ +## Roots and powers + +`sqrt()` finds a square root — useful anywhere the Pythagorean theorem shows up, like the diagonal brace of a square enclosure. + +```python-ref +import math + +side_ft = 4 +diagonal = math.sqrt(side_ft ** 2 + side_ft ** 2) + +print(diagonal) +``` + +### pow + +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`. + +```python-ref +side_ft ** 2 # 16 — an int +math.pow(side_ft, 2) # 16.0 — always a float +``` + +??? tip "Integer square roots with isqrt" + `math.sqrt()` always returns a `float`, even for a perfect square. `math.isqrt()` works on integers only and rounds down, avoiding any floating-point rounding error. + + ```python-ref + math.sqrt(50) # 7.0710678118654755 + math.isqrt(50) # 7 — rounded down, exact + ``` + +??? run "Run a roots and powers example" + All the examples above, combined into one script: + + ```python + import math + + side_ft = 4 + diagonal = math.sqrt(side_ft ** 2 + side_ft ** 2) + + print(diagonal) + print(side_ft ** 2) + print(math.pow(side_ft, 2)) + ``` + +
+ +
+ +## Constants + +`math.pi` is the constant π, accurate to the precision of a `float` — no need to type out `3.14159...` by hand. + +```python-ref +import math + +radius_ft = 3 +circumference = 2 * math.pi * radius_ft + +print(circumference) +``` + +??? tip "inf and nan" + `math.inf` is a value larger than any number, useful as a starting point when searching for a minimum. `math.nan` ("not a number") represents an undefined result, like `0 / 0` in floating-point math — check for it with `math.isnan()`, since `nan == nan` is always `False`. + + ```python-ref + smallest = math.inf + for length in [5, 12, 3.5]: + if length < smallest: + smallest = length + smallest # 3.5 + + math.isnan(math.nan) # True + ``` + +??? run "Run a constants example" + All the examples above, combined into one script: + + ```python + import math + + radius_ft = 3 + circumference = 2 * math.pi * radius_ft + + print(circumference) + + import math + + smallest = math.inf + for length in [5, 12, 3.5]: + if length < smallest: + smallest = length + + print(smallest) + print(math.isnan(math.nan)) + ``` + +
+ +
+ +## Logarithms + +`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. + +```python-ref +import math + +starting = 2 +population = 64 + +generations = math.log2(population / starting) + +print(generations) +``` + +`log()` and `log10()` work the same way in base *e* and base 10, and `exp()` reverses `log()` — raising *e* to a power. + +```python-ref +math.log10(1000) # 3.0 +math.exp(1) # 2.718281828459045 — the same as math.e +``` + +??? run "Run a logarithms example" + All the examples above, combined into one script: + + ```python + import math + + starting = 2 + population = 64 + + generations = math.log2(population / starting) + + print(generations) + print(math.log10(1000)) + print(math.exp(1)) + ``` + +
+ +
+ +## Comparing floats + +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. + +```python-ref +import math + +0.1 + 0.2 == 0.3 # False — a floating-point rounding artifact +math.isclose(0.1 + 0.2, 0.3) # True +``` + +??? warning "Never compare floats with ==" + `0.1 + 0.2` is actually `0.30000000000000004` under the hood — every float is stored as a binary approximation, and `0.1` can't be represented exactly in binary any more than `1/3` can be written exactly in decimal. `==` compares that approximation exactly, so it fails in cases that look like they should match. `math.isclose()` is the fix any time float results are compared, not just when the numbers came from a fraction like this one. + +??? run "Run a comparing floats example" + All the examples above, combined into one script: + + ```python + import math + + print(0.1 + 0.2 == 0.3) + print(math.isclose(0.1 + 0.2, 0.3)) + ``` + +
diff --git a/docs/libraries/re.md b/docs/libraries/re.md new file mode 100644 index 0000000..4687cba --- /dev/null +++ b/docs/libraries/re.md @@ -0,0 +1,203 @@ +--- +description: >- + Regular expressions in Python with the re module: searching, extracting groups, replacing, + and splitting text, with runnable examples. +--- + +# :material-regex:{ .lg .middle } re library + +[Official documentation :material-open-in-new:](https://docs.python.org/3/library/re.html){ target="_blank" } + +The **`re`** module works with regular expressions — patterns that describe text to search for, extract, or replace, more flexible than plain string methods like `.find()` or `.replace()`. + +
+ +## Install { data-card-link="skip" } + +`re` ships with Python's standard library — nothing to install. + +
+ +
+ +## Import { data-card-link="skip" } + +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. + +```python-ref +import re +``` + +| Function | Does | Returns | +|----------|------|---------| +| `search(pattern, text)` | Finds the first match anywhere in `text` | A `Match`, or `None` | +| `findall(pattern, text)` | Finds every match | A list of strings (or tuples, with groups) | +| `sub(pattern, repl, text)` | Replaces every match | A new string | +| `split(pattern, text)` | Splits `text` wherever the pattern matches | A list of strings | + +| Pattern syntax | Matches | +|-----------------|---------| +| `\d` `\w` `\s` | A digit, a word character, whitespace | +| `.` | Any single character | +| `*` `+` `?` | 0 or more, 1 or more, 0 or 1 of the thing before it | +| `{n,m}` | Between `n` and `m` repeats | +| `[...]` | Any one character from the set | +| `^` `$` | Start, end of the text | +| `(...)` | A capturing group | + +
+ +
+ +## Searching for a pattern + +`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. + +```python-ref +import re + +note = "the burmese python measured 12ft at last checkup" +match = re.search(r"\d+ft", note) + +print(match.group()) +``` + +??? tip "Reusing a pattern with re.compile" + Compiling a pattern once with `re.compile()` and calling `.search()`/`.findall()`/etc. on the result is faster than passing the same pattern string to `re.*` repeatedly — worth it once a pattern is reused across many pieces of text. + + ```python-ref + length_pattern = re.compile(r"\d+ft") + length_pattern.search(note).group() # "12ft" + ``` + +??? run "Run a searching example" + All the examples above, combined into one script: + + ```python + import re + + note = "the burmese python measured 12ft at last checkup" + match = re.search(r"\d+ft", note) + + print(match.group()) + + import re + + length_pattern = re.compile(r"\d+ft") + match = length_pattern.search(note) + + print(match.group()) + ``` + +
+ +
+ +## Finding all matches + +`re.findall()` returns every match in the text as a list, instead of stopping at the first one. + +```python-ref +import re + +notes = "ball: 4ft, burmese: 12ft, boa: 8ft" + +print(re.findall(r"\d+ft", notes)) +``` + +### 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. + +```python-ref +notes = "ball: 4ft, burmese: 12ft, boa: 8ft" +re.findall(r"(\w+): (\d+)ft", notes) # [("ball", "4"), ("burmese", "12"), ("boa", "8")] +``` + +??? tip "Naming a group" + `(?P...)` gives a group a name instead of a position, so it can be read back with `.group("name")` on a `Match` — clearer than counting parentheses when a pattern has several groups. + + ```python-ref + match = re.search(r"(?P\w+): (?P\d+)ft", notes) + match.group("species") # "ball" + match.group("length") # "4" + ``` + +??? run "Run a finding all matches example" + All the examples above, combined into one script: + + ```python + import re + + notes = "ball: 4ft, burmese: 12ft, boa: 8ft" + + print(re.findall(r"\d+ft", notes)) + + import re + + notes = "ball: 4ft, burmese: 12ft, boa: 8ft" + print(re.findall(r"(\w+): (\d+)ft", notes)) + + import re + + notes = "ball: 4ft, burmese: 12ft, boa: 8ft" + match = re.search(r"(?P\w+): (?P\d+)ft", notes) + + print(match.group("species"), match.group("length")) + ``` + +
+ +
+ +## Replacing text + +`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. + +```python-ref +import re + +notes = "ball: 4ft, burmese: 12ft, boa: 8ft" + +print(re.sub(r"(\d+)ft", r"\1 feet", notes)) +``` + +??? run "Run a replacing text example" + All the examples above, combined into one script: + + ```python + import re + + notes = "ball: 4ft, burmese: 12ft, boa: 8ft" + + print(re.sub(r"(\d+)ft", r"\1 feet", notes)) + ``` + +
+ +
+ +## Splitting on a pattern + +`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. + +```python-ref +import re + +species_list = "ball, burmese; boa, blood" + +print(re.split(r"[,;]\s*", species_list)) +``` + +??? run "Run a splitting example" + All the examples above, combined into one script: + + ```python + import re + + species_list = "ball, burmese; boa, blood" + + print(re.split(r"[,;]\s*", species_list)) + ``` + +
diff --git a/mkdocs.yml b/mkdocs.yml index 70837cb..131f797 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -23,7 +23,9 @@ nav: - Utilities: - collections: libraries/collections.md - datetime: libraries/datetime.md + - math: libraries/math.md - random: libraries/random.md + - re: libraries/re.md - Data analysis: - csv: libraries/csv.md - matplotlib: libraries/matplotlib.md From dc975720564ee67de6d42c203a7926f4ab07616b Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Tue, 15 Sep 2026 09:57:23 -0700 Subject: [PATCH 05/12] remove inner subsections from homepage library cards --- docs/index.md | 241 ++----------------------------------- docs/stylesheets/extra.css | 16 ++- 2 files changed, 20 insertions(+), 237 deletions(-) diff --git a/docs/index.md b/docs/index.md index 470e0ff..31b0306 100644 --- a/docs/index.md +++ b/docs/index.md @@ -537,31 +537,7 @@ hide: # Add-On Libraries -
-#### 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. - - [**`writing and running a test`**](libraries/pytest.md#writing-and-running-a-test): - [`from the command line`](libraries/pytest.md#from-the-command-line) - - [**`reading a failure`**](libraries/pytest.md#reading-a-failure) - - [**`fixtures`**](libraries/pytest.md#fixtures) - - [**`parametrizing tests`**](libraries/pytest.md#parametrizing-tests) - - [**`testing for exceptions`**](libraries/pytest.md#testing-for-exceptions) - -
-
- -
+
#### Utilities { .pt-homepage-heading }
@@ -572,73 +548,14 @@ hide: Specialized containers with advanced functionality. - [**`Counter`**](libraries/collections.md#counter): - [`+ - & |`](libraries/collections.md#combine) - [`counts[item]`](libraries/collections.md#count) - [`elements`](libraries/collections.md#inspect) - [`most_common`](libraries/collections.md#count) - [`subtract`](libraries/collections.md#update) - [`total`](libraries/collections.md#count) - [`update`](libraries/collections.md#update) - - [**`defaultdict`**](libraries/collections.md#defaultdict): - [`default_factory`](libraries/collections.md#defaultdict) - [`get`](libraries/collections.md#reading-vs-writing) - - [**`namedtuple`**](libraries/collections.md#namedtuple): - [`_asdict`](libraries/collections.md#convert) - [`_field_defaults`](libraries/collections.md#inspect_1) - [`_fields`](libraries/collections.md#inspect_1) - [`_make`](libraries/collections.md#create) - [`_replace`](libraries/collections.md#convert) - [`defaults=`](libraries/collections.md#create) - - [**`deque`**](libraries/collections.md#deque): - [`append`](libraries/collections.md#add) - [`appendleft`](libraries/collections.md#add) - [`clear`](libraries/collections.md#remove) - [`copy`](libraries/collections.md#inspect_2) - [`count`](libraries/collections.md#inspect_2) - [`extend`](libraries/collections.md#add) - [`extendleft`](libraries/collections.md#add) - [`index`](libraries/collections.md#inspect_2) - [`insert`](libraries/collections.md#add) - [`maxlen=`](libraries/collections.md#reorder) - [`pop`](libraries/collections.md#remove) - [`popleft`](libraries/collections.md#remove) - [`remove`](libraries/collections.md#remove) - [`reverse`](libraries/collections.md#reorder) - [`rotate`](libraries/collections.md#reorder) - - [**`OrderedDict`**](libraries/collections.md#ordereddict): - [`==`](libraries/collections.md#compare) - [`move_to_end`](libraries/collections.md#reorder_1) - [`popitem`](libraries/collections.md#reorder_1) - - [**`ChainMap`**](libraries/collections.md#chainmap): - [`maps`](libraries/collections.md#inspect_3) - [`new_child`](libraries/collections.md#extend) - [`parents`](libraries/collections.md#inspect_3) - - [**`User* wrapper`**](libraries/collections.md#user-wrapper-classes): - [`UserDict`](libraries/collections.md#user-wrapper-classes) - [`UserList`](libraries/collections.md#user-wrapper-classes) - [`UserString`](libraries/collections.md#user-wrapper-classes) - - :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. - [`creating a specific date`](libraries/datetime.md#creating-a-specific-date) - [`date`](libraries/datetime.md#creating-dates-and-times) - [`strftime`](libraries/datetime.md#formatting-with-strftime) - :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" } - [`difference between two dates`](libraries/datetime.md#difference-between-two-dates) - [`strptime`](libraries/datetime.md#parsing-a-string-with-strptime) - [`timedelta`](libraries/datetime.md#date-arithmetic) Rounding, roots, constants, and logarithms. - :material-dice-multiple:{ .lg .middle } [__random__](libraries/random.md) @@ -646,13 +563,9 @@ hide: Random numbers, random picks, shuffled order. - [**`randint`**](libraries/random.md#random-numbers) - :material-regex:{ .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" } - [**`choice`**](libraries/random.md#random-selections): - [`sample`](libraries/random.md#sampling-without-replacement) - [`shuffle`](libraries/random.md#shuffling-a-list) Regular expressions: searching, extracting, and replacing text by pattern.
@@ -668,53 +581,23 @@ hide: Reading and writing spreadsheets. - [`writer`](libraries/csv.md#writing-csv-files) - - [`DictReader`](libraries/csv.md#reading-rows-as-dictionaries) - [`reader`](libraries/csv.md#reading-csv-files) - - :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. - [**`line plots`**](libraries/matplotlib.md#line-plots): - [`labels and title`](libraries/matplotlib.md#labels-and-title) - [`multiple lines and a legend`](libraries/matplotlib.md#multiple-lines-and-a-legend) - - [**`bar charts`**](libraries/matplotlib.md#bar-charts) - - [**`scatter plots`**](libraries/matplotlib.md#scatter-plots) - - [**`subplots`**](libraries/matplotlib.md#subplots) - - [**`saving a figure`**](libraries/matplotlib.md#saving-a-figure) - - :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-advanced="card" } Fast numeric arrays, with math applied to a whole array at once instead of item by item. - [**`array operations`**](libraries/numpy.md#array-operations): - [`boolean mask`](libraries/numpy.md#filtering-with-a-boolean-mask) - [`mean`](libraries/numpy.md#aggregating-an-array) - - [`arange`](libraries/numpy.md#building-arrays-without-a-list) - [`ndarray`](libraries/numpy.md#creating-arrays) - - :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-advanced="card" } Tabular data: rows and columns, like a spreadsheet, built on top of NumPy. - [**`DataFrame`**](libraries/pandas.md#building-a-dataframe) - - [**`working with a DataFrame`**](libraries/pandas.md#working-with-a-dataframe): - [`mean`](libraries/pandas.md#summarizing-a-column) - [`sort_values`](libraries/pandas.md#sorting-rows) -
@@ -728,25 +611,11 @@ hide: Reading and writing JSON data: nested dicts and lists, saved to a file or a string. - [`dump`](libraries/json.md#writing-json-files) - - [`load`](libraries/json.md#reading-json-files) - [`nested data`](libraries/json.md#nested-data) - - [`loads`](libraries/json.md#working-with-strings-instead-of-files) - - :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. - [**`get`**](libraries/requests.md#making-a-request): - [`json`](libraries/requests.md#parsing-json) - [`params`](libraries/requests.md#query-parameters) - [`status_code`](libraries/requests.md#checking-the-status-code) - - [**`error handling`**](libraries/requests.md#handling-request-errors) -
@@ -760,46 +629,6 @@ hide: Opening, editing, and saving images, built around one Image object. - [**`why Pillow?`**](libraries/pillow.md#why-pillow) - - [**`Image`**](libraries/pillow.md#the-image): - [`basic operations`](libraries/pillow.md#basic-operations) - [`crop`](libraries/pillow.md#crop) - [`image modes`](libraries/pillow.md#image-modes) - [`opening and saving images`](libraries/pillow.md#opening-and-saving-images) - [`resize`](libraries/pillow.md#resize) - [`rotate and flip`](libraries/pillow.md#rotate-and-flip) - - [**`ImageOps`**](libraries/pillow.md#imageops-module): - [`common ImageOps functions`](libraries/pillow.md#common-imageops-functions) - - [**`ImageDraw`**](libraries/pillow.md#imagedraw-module): - [`shapes and lines`](libraries/pillow.md#shapes-and-lines) - - [**`ImageFont`**](libraries/pillow.md#imagefont-module): - [`loading a font`](libraries/pillow.md#loading-a-font) - - [**`ImageColor`**](libraries/pillow.md#imagecolor-module): - [`converting color names`](libraries/pillow.md#converting-color-names) - - [**`ImageFilter`**](libraries/pillow.md#imagefilter-module): - [`applying a filter`](libraries/pillow.md#applying-a-filter) - - [**`ImageEnhance`**](libraries/pillow.md#imageenhance-module): - [`enhancing an image`](libraries/pillow.md#enhancing-an-image) - - [**`ImageChops`**](libraries/pillow.md#imagechops-module): - [`comparing and combining images`](libraries/pillow.md#comparing-and-combining-images) - - [**`convert`**](libraries/pillow.md#format-conversion): - [`converting between formats`](libraries/pillow.md#converting-between-formats) - - [**`ImageSequence`**](libraries/pillow.md#imagesequence-module): - [`looping over GIF frames`](libraries/pillow.md#looping-over-gif-frames) - - [**`putting it together`**](libraries/pillow.md#putting-it-together): - [`an interactive filter tool`](libraries/pillow.md#an-interactive-filter-tool) - @@ -813,36 +642,18 @@ hide: Creating desktop applications: text, buttons, dropdowns, forms, output, etc. - [**`Tk`**](libraries/tkinter.md#creating-a-window) - - [**`Button`**](libraries/tkinter.md#widgets): - [`Button`](libraries/tkinter.md#button) - [`Entry`](libraries/tkinter.md#entry) - [`Label`](libraries/tkinter.md#label) - - [**`pack`**](libraries/tkinter.md#layout-managers): - [`grid`](libraries/tkinter.md#grid) - [`pack`](libraries/tkinter.md#pack) - - [**`configure`**](libraries/tkinter.md#configuring-widgets): - [`reading and changing options`](libraries/tkinter.md#reading-and-changing-options) - - [**`command`**](libraries/tkinter.md#handling-events): - [`binding events`](libraries/tkinter.md#binding-events) - [`command callbacks`](libraries/tkinter.md#command-callbacks) + + - [**`Style`**](libraries/tkinter.md#styling-with-ttk): - [`customizing a style`](libraries/tkinter.md#customizing-a-style) +
+#### Testing { .pt-homepage-heading } - [**`messagebox`**](libraries/tkinter.md#dialogs): - [`file dialogs`](libraries/tkinter.md#file-dialogs) - [`message boxes`](libraries/tkinter.md#message-boxes) +
- [**`winfo_width`**](libraries/tkinter.md#introspecting-widgets): - [`winfo methods`](libraries/tkinter.md#winfo-methods) +- :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" } - [**`putting it together`**](libraries/tkinter.md#putting-it-together): - [`a simple form`](libraries/tkinter.md#a-simple-form) + Writing and running tests: assertions, fixtures, and parametrizing.
@@ -858,41 +669,7 @@ hide: Real-time image and video analysis, built directly on NumPy arrays: color spaces, edge detection, face detection. - [**`reading, displaying, saving images`**](libraries/opencv.md#reading-displaying-and-saving-images): - [`displaying a window`](libraries/opencv.md#displaying-a-window) - [`imread`](libraries/opencv.md#reading-a-file) - [`saving a file`](libraries/opencv.md#saving-a-file) - - [**`drawing`**](libraries/opencv.md#drawing-shapes-and-text): - [`shapes and lines`](libraries/opencv.md#shapes-and-lines) - [`text`](libraries/opencv.md#text) - - [**`color spaces`**](libraries/opencv.md#color-spaces): - [`cvtColor`](libraries/opencv.md#converting-color-spaces) - - [**`CascadeClassifier`**](libraries/opencv.md#face-detection-with-cascade-classifiers): - [`detecting and labeling faces`](libraries/opencv.md#detecting-and-labeling-faces) - - [**`VideoCapture`**](libraries/opencv.md#working-with-video): - [`reading frames`](libraries/opencv.md#reading-frames) - - [**`basic operations`**](libraries/opencv.md#basic-operations): - [`cropping`](libraries/opencv.md#cropping) - [`resize`](libraries/opencv.md#resize) - [`rotating`](libraries/opencv.md#rotating) - - [**`thresholding, edge detection`**](libraries/opencv.md#thresholding-and-edge-detection): - [`Canny`](libraries/opencv.md#edge-detection) - [`threshold`](libraries/opencv.md#threshold) - - [**`blurring`**](libraries/opencv.md#blurring): - [`gaussian blur`](libraries/opencv.md#gaussian-blur) - - [**`contours`**](libraries/opencv.md#contours): - [`finding and drawing contours`](libraries/opencv.md#finding-and-drawing-contours) - - diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index c323b8a..99f5922 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -808,11 +808,16 @@ input:checked + .md-consent__settings { /* 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(3)+Desktop UIs(1), - Data analysis(4), APIs(2)+Image editing(1)+Computer vision(1) — every card - the same ~1/4 width as the ones above. 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. */ + 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 + 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 + 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 { grid-template-columns: repeat(4, 1fr); @@ -831,6 +836,7 @@ input:checked + .md-consent__settings { .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 */ /* pt-lib--N above is sized for the full card count; Essentials mode hides some, leaving boxes too wide. essentials_toggle.js recomputes the From 7b0d12cc05571d8a3c3fc0e506c79f6698567ae0 Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Tue, 15 Sep 2026 17:36:46 -0700 Subject: [PATCH 06/12] group library import and install under setup --- docs/libraries/collections.md | 15 +++------- docs/libraries/csv.md | 12 ++------ docs/libraries/datetime.md | 12 ++------ docs/libraries/index.md | 53 +++++++++++++++++++++++++++++++++++ docs/libraries/json.md | 12 ++------ docs/libraries/math.md | 12 ++------ docs/libraries/matplotlib.md | 8 +----- docs/libraries/numpy.md | 8 +----- docs/libraries/opencv.md | 8 +----- docs/libraries/pandas.md | 8 +----- docs/libraries/pillow.md | 8 +----- docs/libraries/pytest.md | 8 +----- docs/libraries/random.md | 12 ++------ docs/libraries/re.md | 12 ++------ docs/libraries/requests.md | 8 +----- docs/libraries/tkinter.md | 12 ++------ 16 files changed, 78 insertions(+), 130 deletions(-) diff --git a/docs/libraries/collections.md b/docs/libraries/collections.md index 4f41f04..7bfdea1 100644 --- a/docs/libraries/collections.md +++ b/docs/libraries/collections.md @@ -35,18 +35,11 @@ built-in [`str`](../types.md#strings) [`list`](../collections.md#lists) [`dict`]
-## Install { data-card-link="skip" } +## Setup { data-card-link="skip" } -`collections` ships with Python's standard library — nothing to install. - -
- -
- -## Import { data-card-link="skip" } - -Each class is imported individually by name, rather than through a `collections.` prefix — -so the import line differs per class, shown under its own "Import" heading below. +`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 +line differs per class, shown under its own "Import" heading below.
diff --git a/docs/libraries/csv.md b/docs/libraries/csv.md index e0a8a59..bb419f5 100644 --- a/docs/libraries/csv.md +++ b/docs/libraries/csv.md @@ -12,17 +12,9 @@ The **`csv`** module reads and writes CSV ("comma-separated values") files — a
-## Install { data-card-link="skip" } +## Setup { data-card-link="skip" } -`csv` ships with Python's standard library — nothing to install. - -
- -
- -## Import { data-card-link="skip" } - -The whole module is used through the `csv.` prefix, so a plain import is all you need. +`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. ```python-ref import csv diff --git a/docs/libraries/datetime.md b/docs/libraries/datetime.md index ff0ecc4..76db70f 100644 --- a/docs/libraries/datetime.md +++ b/docs/libraries/datetime.md @@ -12,17 +12,9 @@ The **`datetime`** module is Python's standard library for working with dates an
-## Install { data-card-link="skip" } +## Setup { data-card-link="skip" } -`datetime` ships with Python's standard library — nothing to install. - -
- -
- -## Import { data-card-link="skip" } - -Each class below is imported individually by name, rather than through a `datetime.` prefix. +`datetime` ships with Python's standard library — nothing to install. Each class below is imported individually by name, rather than through a `datetime.` prefix. ```python-ref from datetime import date, datetime, timedelta diff --git a/docs/libraries/index.md b/docs/libraries/index.md index 547bf95..30aa7de 100644 --- a/docs/libraries/index.md +++ b/docs/libraries/index.md @@ -331,6 +331,59 @@ Libraries allow us to apply Python to real tasks. These are a few popular ones,
+
+#### 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" } + + Building small movement-based games with a virtual pen that moves around a window. + + [**`Concepts`**](turtle.md#concepts) + + [**`The screen`**](turtle.md#the-screen): + [`Setup`](turtle.md#setup) + [`Tracer and updates`](turtle.md#tracer-and-updates) + [`Background`](turtle.md#background) + [`Closing the window`](turtle.md#closing-the-window) + + [**`The turtle`**](turtle.md#the-turtle): + [`Shape`](turtle.md#shape) + [`Show or hide the turtle`](turtle.md#show-or-hide-the-turtle) + [`Custom images`](turtle.md#custom-images) + [`The turtle as the game itself`](turtle.md#the-turtle-as-the-game-itself) + [`Ink`](turtle.md#ink) + [`Colors`](turtle.md#colors) + [`Pencolor vs fillcolor`](turtle.md#pencolor-vs-fillcolor) + [`Ink as the game itself`](turtle.md#ink-as-the-game-itself) + [`Drawing shapes`](turtle.md#drawing-shapes) + [`Dot`](turtle.md#dot) + [`Rectangle`](turtle.md#rectangle) + [`Stamping`](turtle.md#stamping) + [`Text`](turtle.md#text) + [`Positions and motion`](turtle.md#positions-and-motion) + [`The turtle's own position`](turtle.md#the-turtles-own-position) + [`Many positions at once`](turtle.md#many-positions-at-once) + + [**`ontimer`**](turtle.md#the-game-loop) + + [**`Input`**](turtle.md#input): + [`Keyboard`](turtle.md#keyboard) + [`Mouse`](turtle.md#mouse) + [`Dialogs`](turtle.md#dialogs) + + [**`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 } diff --git a/docs/libraries/json.md b/docs/libraries/json.md index 173d139..fa85b66 100644 --- a/docs/libraries/json.md +++ b/docs/libraries/json.md @@ -12,17 +12,9 @@ The **`json`** module reads and writes JSON ("JavaScript Object Notation") data
-## Install { data-card-link="skip" } +## Setup { data-card-link="skip" } -`json` ships with Python's standard library — nothing to install. - -
- -
- -## Import { data-card-link="skip" } - -The whole module is used through the `json.` prefix, so a plain import is all you need. +`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. ```python-ref import json diff --git a/docs/libraries/math.md b/docs/libraries/math.md index 010b2cd..c2482bc 100644 --- a/docs/libraries/math.md +++ b/docs/libraries/math.md @@ -12,17 +12,9 @@ The **`math`** module extends Python's built-in arithmetic with functions it doe
-## Install { data-card-link="skip" } +## Setup { data-card-link="skip" } -`math` ships with Python's standard library — nothing to install. - -
- -
- -## Import { data-card-link="skip" } - -The whole module is used through the `math.` prefix, so a plain import is all you need. +`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. ```python-ref import math diff --git a/docs/libraries/matplotlib.md b/docs/libraries/matplotlib.md index fecf7d8..fbd0ace 100644 --- a/docs/libraries/matplotlib.md +++ b/docs/libraries/matplotlib.md @@ -14,18 +14,12 @@ matplotlib is an open-source project, funded by nonprofit [NumFOCUS](https://num
-## Install { data-card-link="skip" } +## Setup { data-card-link="skip" } ```bash pip install matplotlib ``` -
- -
- -## Import { data-card-link="skip" } - matplotlib's plotting interface lives in its `pyplot` submodule, conventionally imported under the alias `plt` — used throughout this page and in virtually every codebase that imports it. ```python-ref diff --git a/docs/libraries/numpy.md b/docs/libraries/numpy.md index 5f77bd8..c16f79d 100644 --- a/docs/libraries/numpy.md +++ b/docs/libraries/numpy.md @@ -14,18 +14,12 @@ NumPy is an open-source project, with fiscal sponsorship from the nonprofit [Num
-## Install { data-card-link="skip" } +## Setup { data-card-link="skip" } ```bash pip install numpy ``` -
- -
- -## Import { data-card-link="skip" } - `np` is the near-universal alias for NumPy — used throughout this page and in virtually every codebase that imports it. ```python-ref diff --git a/docs/libraries/opencv.md b/docs/libraries/opencv.md index 9149415..9f84875 100644 --- a/docs/libraries/opencv.md +++ b/docs/libraries/opencv.md @@ -14,18 +14,12 @@ OpenCV is stewarded by nonprofit [OpenCV.org](https://opencv.org/).
-## Install { data-card-link="skip" } +## Setup { data-card-link="skip" } ```bash pip install opencv-python ``` -
- -
- -## Import { data-card-link="skip" } - OpenCV's package name (`opencv-python`) doesn't match its import name — it's always imported as `cv2`. ```python-ref diff --git a/docs/libraries/pandas.md b/docs/libraries/pandas.md index eff2edb..516f894 100644 --- a/docs/libraries/pandas.md +++ b/docs/libraries/pandas.md @@ -14,18 +14,12 @@ pandas is an open-source project, funded by nonprofit [NumFOCUS](https://numfocu
-## Install { data-card-link="skip" } +## Setup { data-card-link="skip" } ```bash pip install pandas ``` -
- -
- -## Import { data-card-link="skip" } - `pd` is the near-universal alias for pandas — used throughout this page and in virtually every codebase that imports it. ```python-ref diff --git a/docs/libraries/pillow.md b/docs/libraries/pillow.md index c5152b0..f15c524 100644 --- a/docs/libraries/pillow.md +++ b/docs/libraries/pillow.md @@ -14,18 +14,12 @@ Pillow is an open-source project maintained by volunteer contributors.
-## Install { data-card-link="skip" } +## Setup { data-card-link="skip" } ```bash pip install pillow ``` -
- -
- -## Import { data-card-link="skip" } - Pillow's package name (`pillow`) doesn't match its import name — it's imported as `PIL`, and `Image` specifically is used throughout this page. ```python-ref diff --git a/docs/libraries/pytest.md b/docs/libraries/pytest.md index 6538a38..7f541a7 100644 --- a/docs/libraries/pytest.md +++ b/docs/libraries/pytest.md @@ -14,18 +14,12 @@ pytest is an open-source project maintained by volunteer contributors.
-## Install { data-card-link="skip" } +## Setup { data-card-link="skip" } ```bash pip install pytest ``` -
- -
- -## Import { data-card-link="skip" } - Most of what pytest does — discovering `test_*` functions and checking plain `assert` statements — needs no import at all. A plain `import pytest` is only needed for its extra tools: fixtures, marks, and `pytest.raises`. ```python-ref diff --git a/docs/libraries/random.md b/docs/libraries/random.md index f0bc210..60d73ef 100644 --- a/docs/libraries/random.md +++ b/docs/libraries/random.md @@ -10,17 +10,9 @@ The **`random`** module generates pseudo-random numbers and makes random selecti
-## Install { data-card-link="skip" } +## Setup { data-card-link="skip" } -`random` ships with Python's standard library — nothing to install. - -
- -
- -## Import { data-card-link="skip" } - -The whole module is used through the `random.` prefix, so a plain import is all you need. +`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. ```python-ref import random diff --git a/docs/libraries/re.md b/docs/libraries/re.md index 4687cba..9377b9d 100644 --- a/docs/libraries/re.md +++ b/docs/libraries/re.md @@ -12,17 +12,9 @@ The **`re`** module works with regular expressions — patterns that describe te
-## Install { data-card-link="skip" } +## Setup { data-card-link="skip" } -`re` ships with Python's standard library — nothing to install. - -
- -
- -## Import { data-card-link="skip" } - -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. +`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. ```python-ref import re diff --git a/docs/libraries/requests.md b/docs/libraries/requests.md index 740e4d9..4973a0f 100644 --- a/docs/libraries/requests.md +++ b/docs/libraries/requests.md @@ -14,18 +14,12 @@ requests is an open-source project maintained by volunteer contributors.
-## Install { data-card-link="skip" } +## Setup { data-card-link="skip" } ```bash pip install requests ``` -
- -
- -## Import { data-card-link="skip" } - The whole module is used through the `requests.` prefix, so a plain import is all you need. ```python-ref diff --git a/docs/libraries/tkinter.md b/docs/libraries/tkinter.md index a708568..744e3cf 100644 --- a/docs/libraries/tkinter.md +++ b/docs/libraries/tkinter.md @@ -12,17 +12,9 @@ description: >-
-## Install { data-card-link="skip" } +## Setup { data-card-link="skip" } -Tkinter ships with the standard library — no extra install is needed on your own machine. - -
- -
- -## Import { data-card-link="skip" } - -`tk` is the near-universal alias for the base module; the themed `ttk` widgets (used throughout this page) are imported separately. +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. ```python-ref import tkinter as tk From 434119b937dd36dd34df2f5cea9458f5111ea490 Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Wed, 16 Sep 2026 13:06:24 -0700 Subject: [PATCH 07/12] new turtle game page --- docs/index.md | 13 + docs/libraries/index.md | 3 +- docs/libraries/turtle.md | 707 +++++++++++++++++++++++++++++++++++++++ mkdocs.yml | 2 + 4 files changed, 724 insertions(+), 1 deletion(-) create mode 100644 docs/libraries/turtle.md diff --git a/docs/index.md b/docs/index.md index 31b0306..bea7d80 100644 --- a/docs/index.md +++ b/docs/index.md @@ -645,6 +645,19 @@ hide:
+
+#### 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" } + + Building small movement-based games with a virtual pen that moves around a window. + +
+
+
#### Testing { .pt-homepage-heading } diff --git a/docs/libraries/index.md b/docs/libraries/index.md index 30aa7de..b73afc3 100644 --- a/docs/libraries/index.md +++ b/docs/libraries/index.md @@ -344,7 +344,7 @@ Libraries allow us to apply Python to real tasks. These are a few popular ones, [**`Concepts`**](turtle.md#concepts) [**`The screen`**](turtle.md#the-screen): - [`Setup`](turtle.md#setup) + [`Screen setup`](turtle.md#screen-setup) [`Tracer and updates`](turtle.md#tracer-and-updates) [`Background`](turtle.md#background) [`Closing the window`](turtle.md#closing-the-window) @@ -360,6 +360,7 @@ Libraries allow us to apply Python to real tasks. These are a few popular ones, [`Ink as the game itself`](turtle.md#ink-as-the-game-itself) [`Drawing shapes`](turtle.md#drawing-shapes) [`Dot`](turtle.md#dot) + [`Circle`](turtle.md#circle) [`Rectangle`](turtle.md#rectangle) [`Stamping`](turtle.md#stamping) [`Text`](turtle.md#text) diff --git a/docs/libraries/turtle.md b/docs/libraries/turtle.md new file mode 100644 index 0000000..4ee7ccd --- /dev/null +++ b/docs/libraries/turtle.md @@ -0,0 +1,707 @@ +--- +description: >- + Building small movement-based games in Python with the turtle module: window setup, + positions and motion, drawing shapes, the animation loop, keyboard and mouse input, and + collision detection. +--- + +# :material-turtle:{ .lg .middle } Turtle library + +[Official documentation :material-open-in-new:](https://docs.python.org/3/library/turtle.html){ target="_blank" } + +
+ +## Concepts + +**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. + +The origins of this library predate ordinary people owning computers: it comes from Logo, a language built in 1967 for teaching programming, whose original "turtle" was an actual robot that dragged a pen across a sheet of paper on the floor. + +
+ +
+ +## Setup { data-card-link="skip" } + +`turtle` ships with the standard library — nothing to install. + +```python-ref +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`). + +
+ +
+ +## The screen + +Everything gets drawn inside one window — the screen. + +### Screen setup + +Start by creating a window.. + +```python-ref +setup(500, 500) # width, height +title('My Game') # optional +``` + +### Background + +A solid color or a full image, set once on the window itself — not something that needs redrawing every frame. + +```python-ref +bgcolor('skyblue') # a solid color +bgpic('landscape.gif') # or a full image, stretched to fit the window +``` + +Anything more custom than a flat color or a single picture — a gradient, a tiled pattern, a drawn horizon — is drawn by hand instead, using the same shapes from Drawing shapes below. Unlike `bgcolor()`/`bgpic()`, a hand-drawn background has to be part of the loop, since `clear()` erases it along with everything else each frame. + +```python-ref +clear() +rectangle(Vec2D(-200, -200), 400, 400, 'skyblue') # backdrop, drawn first +# ... draw everything else on top ... +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. + +### Clear screen + +`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 + +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. + +| Format | Example | +|---|---| +| A named color string | `'skyblue'` | +| A hex string | `'#33cc8c'` | +| An RGB tuple | `(0.2, 0.8, 0.5)` — each value `0.0`–`1.0` by default | + +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 + +`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. + +```python-ref +exitonclick() # instead of done() — click anywhere to quit +``` + + +
+ +
+ +## The 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 + +#### Show or hide + +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. + +```python-ref +hideturtle() # ht() — hide it +showturtle() # st() — show it again +isvisible() # True or False +``` + +#### Shape, color, size + +`shape(shape_name)` switches between every built-in shapes. + +| Shape name | Looks like | +|---|---| +| `'classic'` | A small, thin-tailed arrow — the default. | +| `'arrow'` | A plain triangular arrowhead, larger than `'classic'`. | +| `'turtle'` | A small turtle outline. | +| `'circle'` | A filled circle. | +| `'square'` | A filled square. | +| `'triangle'` | A filled triangle. | +| `'blank'` | Nothing at all — another way to hide the turtle, besides calling `hideturtle()`. | + + +`color()` sets its outline and fill, and can be any color format as described in the above [colors section](#colors). + +```python-ref +shape('turtle') +shapesize(2) # scale it up 2x +color('darkgreen', 'green') # outline, fill +``` + +#### Custom images + +`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. + +```python-ref +register_shape('snake.gif') +``` + +#### 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()`. + +```python-ref +shape('turtle') +color('green') + +def move(): + global player + player = player + aim + goto(player) # the turtle itself is what moves on screen + ontimer(move, 100) +``` + +That suits something like a maze runner or a chase game — one visible character the player steers, with everything else (walls, an enemy) drawn separately around it. + +Being a visible, real turtle also means it can be clicked directly: `onclick(function)` fires only when the click lands on the turtle's own shape — unlike `onscreenclick()` (Input), which fires no matter where on the window the click happens. + +```python-ref +def on_hit(x, y): + print('hit!') + +onclick(on_hit) +``` + +### Trace movement + +#### With tracer + +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. + +If you don't want that, the alternative is turning the `tracer()` off. Every move then happens instantly, with nothing new appearing on screen until you call `update()`. `tracer()` can be switched on or off again at any point in a script, though turning it off before the first `update()` avoids a first frame flashing briefly. + +```python-ref +tracer(False) + +update() # if tracer is off, then rely on update() to redraw things +``` + +A related but separate setting — `speed(n)` controls how fast each individual `forward()`/`goto()` animates, from `1` (slowest) to `10` (fastest), or `0` for no animation delay at all. It only matters while `tracer()` is left on; with `tracer(False)`, nothing animates regardless of speed. + +??? tip "Partial animation" + `tracer()` also accepts two numbers, `tracer(n, delay)` — show only every `n`-th update, with `delay` milliseconds between them, instead of turning animation off completely. Useful for speeding up something slow and complex without losing the animation altogether. + +??? tip "no_animation() block" + A context manager wrapping the same idea as `tracer(False)`/`tracer(True)` — animation is off for whatever runs inside the block, then back on (and shown) once it exits. The same `with` pattern as [opening a file](../files.md#opening-a-file), applied to animation instead of a file handle. + + ```python-ref + with no_animation(): + circle(50) # drawn instantly, all at once + ``` + +#### Without tracer + +```python-ref +setup(420, 420, 370, 0) +tracer(False) +``` + +With `tracer(False)`, nothing new appears on screen until `update()` is called — normally once per frame, after everything for that frame has been drawn, so a whole frame gets drawn and shown at once instead of stroke-by-stroke. `clear()` wipes the previous frame's drawing first, so shapes don't pile up on top of each other. + +```python-ref +clear() +# ... draw everything for this frame ... +update() +``` + +### Ink + +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. + +| Function | What it does | +|---|---| +| `up()` / `down()` | Lift or lower the pen — move without drawing, or draw a line while moving. | +| `isdown()` | Return whether the pen is currently down. | +| `pensize(width)` | Set the line's thickness. | +| `color(outline_color, fill_color)` | Set colors for outline and fill. | +| `pencolor(outline_color)` | Set outline color. | +| `fillcolor(fill_color)` | Set fill color. | + +```python-ref +color('black', 'yellow') # set outline and fill at once +down() +pensize(3) +forward(50) # draws a 3px-thick line +up() +isdown() # False +goto(0, 0) # moves back without drawing +``` + +### Drawing shapes + +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()`. + +| Function | What it does | +|---|---| +| `clear()` | Erase the previous frame's drawing. | +| `up()` / `down()` | Lift or lower the pen — move without drawing, or draw a line while moving. | +| `goto(x, y)` | Move the pen to an absolute position (also accepts a single `Vec2D`). | +| `forward(distance)` | Move the pen forward in its current heading, drawing a line if the pen is down. | +| `left(angle)` | Turn the pen's heading, in degrees. | +| `color(fill_color)` | Set the pen's fill/outline color. | +| `begin_fill()` / `end_fill()` | Start/stop filling the shape traced in between. | +| `pensize(width)` | Set the outline's thickness. | +| `update()` | Show everything drawn since the last `update()`. | + +`clear()` always comes first and `update()` always comes last in a frame — everything in between is whatever needs drawing that frame. Within that: `up()` before moving somewhere without a line trailing behind, `down()` before tracing an outline; `begin_fill()` right before that outline, `end_fill()` right after it, with nothing in between that isn't part of the shape. + +```python-ref +def square(point, size, fill_color): + """Draw a filled square centered on point.""" + x, y = point + up() + goto(x - size / 2, y - size / 2) + down() + color(fill_color) + begin_fill() + for _ in range(4): + forward(size) + left(90) + end_fill() +``` + +#### Dot + +A filled circle, built into turtle directly — no custom function needed. `dot(diameter, color)` draws it centered on wherever the pen currently is. + +```python-ref +up() +goto(0, 0) +dot(20, 'green') +``` + +#### Circle + +`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. + +```python-ref +circle(50) +``` + +An `extent` (an angle) draws only part of the circle — an arc, or a pie-slice shape once combined with `begin_fill()`/`end_fill()` — instead of the whole thing. + +```python-ref +begin_fill() +circle(50, 90) # a quarter-circle arc +end_fill() +``` + +`steps` swaps the smooth curve for a regular polygon with that many sides instead — the same `forward()`/`left()` loop `square()` uses by hand, done automatically. + +```python-ref +circle(50, steps=6) # a hexagon +``` + +#### Rectangle + +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. + +```python-ref +def rectangle(point, width, height, fill_color): + """Draw a filled rectangle with point as its bottom-left corner.""" + x, y = point + up() + goto(x, y) + down() + color(fill_color) + begin_fill() + for _ in range(2): + forward(width) + left(90) + forward(height) + left(90) + end_fill() +``` + +#### Stamping + +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)`. + +```python-ref +shape('circle') +goto(food) +stamp_id = stamp() +``` + +#### Text + +`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. + +```python-ref +up() +goto(0, 180) +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 + +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. + +```python-ref +from turtle import Vec2D + +ball = Vec2D(0, 0) +aim = Vec2D(3, 5) +ball = ball + aim # Vec2D(3, 5) — moved by aim +x, y = ball # unpack like any other tuple — 3, 5 +``` + +??? note "Vec2D is immutable" + `Vec2D` has no `.x`/`.y` attributes to assign to — like any tuple, it can't be changed in place. Moving something means reassigning the variable to a brand-new `Vec2D`, not editing the old one. + + ```python-ref + aim = Vec2D(0, -10) + aim = Vec2D(10, 0) # a new Vec2D — not aim.x = 10 + ``` + +??? tip "Reassigning from inside a function" + Reassigning a global variable's name from inside a function needs `global`, covered on [Functions](../functions.md#local-vs-global-variables) — a game typically has at least one small function whose only job is reassigning a position or direction this way. *Mutating* something in place instead (`trail.append(...)`, `paddles[1] = paddles[1] + Vec2D(0, 20)`, both from "Many positions at once" below) doesn't need `global`, since the name itself is never reassigned — only reassignment does. + + ```python-ref + aim = Vec2D(0, -10) + + def change(x, y): + global aim + aim = Vec2D(x, y) + ``` + +#### 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. + +```python-ref +goto(50, 30) +here = pos() # Vec2D(50, 30) +``` + +`towards(point)` returns the angle from the turtle's current position toward another point, for aiming one thing at another instead of moving toward it directly. `setheading(angle)` then turns the pen to face that angle, in degrees, before `forward()` moves it. + +```python-ref +setheading(towards(ball)) +forward(5) +``` + +#### 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](../collections.md). + +```python-ref +trail = [Vec2D(10, 0)] +trail.append(trail[-1] + aim) # grow by one at the end +trail.pop(0) # shrink by one at the start +``` + +```python-ref +paddles = {1: Vec2D(-200, 0), 2: Vec2D(190, 0)} +paddles[1] = paddles[1] + Vec2D(0, 20) # move just one of them +``` + +
+ +
+ +## The 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. + +| Function | What it does | +|---|---| +| `ontimer(function, ms)` | Run a function once, after a delay — the basis of the game loop. | +| `done()` | Keep the window open, listening for scheduled calls. | + +Call the loop function once, by hand, to draw the first frame — after that, it reschedules itself with `ontimer()` every time it runs. + +```python-ref +def move(): + # ... update positions, redraw the screen ... + ontimer(move, 100) # call move() again in 100ms + +move() # kick off the first frame +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. + + ```python-ref + from random import randrange + + if randrange(10) == 0: + entities.append(new_entity()) + + while entities and not inside(entities[0]): + entities.pop(0) + ``` + +### done() + +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. + +While it waits, it watches for the scheduled calls and input registered earlier — `ontimer()`, keyboard clicks, mouse clicks — and fires them as they come in. Those registering functions don't wait around themselves; each one just notes down a function to run later and immediately moves on. Without a final `done()` (or `mainloop()`, an alias for the same thing) to block and keep the window alive, the script would reach its own end and exit before any of that registered work got a chance to run. + +- **Before `done()`:** window setup, turtle setup, the function definitions, and the one manual call that kicks off the first frame. +- **`done()` itself:** called exactly once, by itself, as the very last line. +- **After `done()`:** nothing. That line never finishes, so anything placed below it never runs. + +
+ +
+ +## Input + +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 + +`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. + +| Function | What it does | +|---|---| +| `listen()` | Start paying attention to keyboard events. | +| `onkey(function, key)` | Run a function, with no arguments, whenever a key is pressed. | + +```python-ref +def change(x, y): + global aim + aim = Vec2D(x, y) + +listen() +onkey(lambda: change(10, 0), 'Right') +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 + +`onscreenclick(function)` calls a function every time the window is clicked, passing the click's x and y coordinates as arguments. + +| Function | What it does | +|---|---| +| `onscreenclick(function)` | Run a function, passed the click's x/y, whenever the window is clicked. | + +```python-ref +def tap(x, y): + global ball + ball = ball + Vec2D(0, 30) + +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 + +`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. + +Unlike everything else on this page, **a dialog pauses until it's answered.** + +```python-ref +name = textinput('Player name', 'Enter your name:') +lives = numinput('Lives', 'How many lives?', default=3, minval=1, maxval=5) +``` + +
+ +
+ +## Detecting collisions + +Many games reduce to the same question: is this position touching that one? + +```python-ref +def inside(point): + """Return True if point is within the screen's boundaries.""" + x, y = point + return -200 < x < 200 and -200 < y < 200 +``` + +### Distance + +`abs()` on a `Vec2D` returns its length — subtracting two positions first gives the distance between them, without writing out a square root by hand. + +```python-ref +paddle = Vec2D(-200, 0) +close_enough = abs(ball - paddle) < 15 +``` + +### Overlap + +Checking whether a point falls within a range — a paddle's height, say — is a plain comparison, no vector math needed. + +```python-ref +_, paddle_y = paddle +_, ball_y = ball + +low = paddle_y +high = paddle_y + 50 +touching = low <= ball_y <= high +``` + +### Membership + +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. + +```python-ref +head = trail[-1] + aim +crashed = head in trail +``` + +
+ +
+ +## Common patterns + +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. + +**A single controlled object** — one position (Positions and motion), moved by keyboard or mouse input (Input), redrawn every frame (The game loop). + +```python-ref +position = starting point +direction = nothing, to start + +def on_key(new_direction): + change direction to new_direction + +def move(): + position = position + direction + redraw the object at its new position + schedule the next frame +``` + +**A trail that grows** — a list of positions instead of one (Many positions at once), growing at one end and shrinking at the other as the controlled object moves, checked against its own history for a collision (Membership). + +```python-ref +trail = [starting position] +direction = starting direction + +def move(): + new_head = trail[-1] + direction + + if new_head is out of bounds or new_head in trail: + stop — game over + + trail.append(new_head) + if new_head did NOT reach a target: + trail.pop(0) # shrink back down to the same length + # otherwise leave the tail alone — the trail grows by one + + redraw every position in trail + schedule the next frame +``` + +**Several independent entities** — more than one position tracked at once: a dict keyed by name or number for entities that stick around the whole game (Many positions at once), or a list that grows and shrinks as entities come and go over time (The game loop's "Spawning and removing things over time"). + +```python-ref +entities = [] # or {}, for ones with names rather than a changing count + +def move(): + for each entity in entities: + move it + + occasionally, append a new entity + remove any entity that's drifted off-screen or otherwise stopped mattering + + redraw every entity + schedule the next frame +``` + +**Reacting to a collision** — once two positions are found to be touching (Distance, Overlap, Membership, or the boundary check that opens Detecting collisions), something specific has to change as a result — the collision check on its own doesn't do anything. + +```python-ref +def move(): + # ... update positions ... + + if touching(a, b): + one of: + stop entirely, without scheduling another frame # a game-ending collision + change direction # a bouncing collision + update score, or remove one of the two # a scoring collision + + redraw everything + schedule the next frame +``` + +**Bouncing off a boundary** — a special case of reacting to a collision, common enough on its own: hitting an edge flips the *component* of direction pointing into it, and leaves the other one alone, so the bounce looks like a reflection instead of a stop or a reversal. + +```python-ref +def move(): + position = position + direction + x, y = position + dx, dy = direction + + if x is past the left or right edge: + direction = Vec2D(-dx, dy) # only the x part flips + if y is past the top or bottom edge: + direction = Vec2D(dx, -dy) # only the y part flips + + redraw everything + schedule the next frame +``` + +**Multiple players** — more than one controlled object (Several independent entities), each moved by its own subset of key bindings instead of one shared direction. + +```python-ref +players = {1: starting position, 2: another starting position} + +def move_player(which, change): + players[which] = players[which] + change + +on_key(lambda: move_player(1, up), key_for_player_1_up) +on_key(lambda: move_player(2, up), key_for_player_2_up) +# ... one binding per player, per direction ... +``` + +**A permanent trail instead of redrawing** — leaving the pen down the whole time (Ink as the game itself) instead of lifting it to reposition, so movement itself draws something that's never erased, rather than a shape cleared and redrawn every frame. + +```python-ref +def move(): + position = position + direction + goto(position) # pen stays down — this itself draws the trail + + if position in trail: + stop entirely # crossed its own ink + + trail.append(position) + schedule the next frame +``` + +Mixing and matching these — a controlled object *and* a growing trail, say, or several players *and* a score — is how a specific game takes shape from these general pieces. + +
+ +
+ +## More advanced games { data-card-link="skip" } + +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/mkdocs.yml b/mkdocs.yml index 131f797..96862a6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -40,6 +40,8 @@ nav: - OpenCV: libraries/opencv.md - Desktop UIs: - Tkinter: libraries/tkinter.md + - Games: + - turtle: libraries/turtle.md - Testing: - pytest: libraries/pytest.md From 9f9b1eb82be3b0fcdf01a82b8439f55264cf4293 Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Wed, 16 Sep 2026 13:26:22 -0700 Subject: [PATCH 08/12] update regex icon --- docs/index.md | 2 +- docs/libraries/index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index bea7d80..bf3b7c8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -563,7 +563,7 @@ hide: Random numbers, random picks, shuffled order. -- :material-regex:{ .lg .middle } [__re__](libraries/re.md) +- :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. diff --git a/docs/libraries/index.md b/docs/libraries/index.md index b73afc3..e2900f9 100644 --- a/docs/libraries/index.md +++ b/docs/libraries/index.md @@ -122,7 +122,7 @@ Libraries allow us to apply Python to real tasks. These are a few popular ones, [`sample`](random.md#sampling-without-replacement) [`shuffle`](random.md#shuffling-a-list) -- :material-regex:{ .lg .middle } [__re__](re.md) +- :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. From a8dce2f2c389173c04fd4a936963cd1acd9d96f1 Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Wed, 16 Sep 2026 13:48:28 -0700 Subject: [PATCH 09/12] turtle index card edits --- docs/index.md | 2 +- docs/libraries/index.md | 43 ++++++++++++++++++++-------------------- docs/libraries/turtle.md | 10 +++++----- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/docs/index.md b/docs/index.md index bf3b7c8..7ff3cce 100644 --- a/docs/index.md +++ b/docs/index.md @@ -653,7 +653,7 @@ hide: - :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" } - Building small movement-based games with a virtual pen that moves around a window. + Build small movement-based games with a pen cursor.
diff --git a/docs/libraries/index.md b/docs/libraries/index.md index e2900f9..2bc60ab 100644 --- a/docs/libraries/index.md +++ b/docs/libraries/index.md @@ -339,41 +339,42 @@ Libraries allow us to apply Python to real tasks. These are a few popular ones, - :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" } - Building small movement-based games with a virtual pen that moves around a window. + Build small movement-based games with a pen cursor. [**`Concepts`**](turtle.md#concepts) - [**`The screen`**](turtle.md#the-screen): - [`Screen setup`](turtle.md#screen-setup) - [`Tracer and updates`](turtle.md#tracer-and-updates) + [**`Screen`**](turtle.md#the-screen): [`Background`](turtle.md#background) - [`Closing the window`](turtle.md#closing-the-window) + [`Clear`](turtle.md#clear-screen) + [`Close`](turtle.md#closing-the-window) + [`Colors`](turtle.md#colors) + [`Setup`](turtle.md#screen-setup) - [**`The turtle`**](turtle.md#the-turtle): - [`Shape`](turtle.md#shape) - [`Show or hide the turtle`](turtle.md#show-or-hide-the-turtle) + [**`turtle cursor`**](turtle.md#the-turtle-cursor): + [`Circle`](turtle.md#circle) + [`Color`](turtle.md#shape-color-size) [`Custom images`](turtle.md#custom-images) - [`The turtle as the game itself`](turtle.md#the-turtle-as-the-game-itself) - [`Ink`](turtle.md#ink) - [`Colors`](turtle.md#colors) - [`Pencolor vs fillcolor`](turtle.md#pencolor-vs-fillcolor) - [`Ink as the game itself`](turtle.md#ink-as-the-game-itself) - [`Drawing shapes`](turtle.md#drawing-shapes) [`Dot`](turtle.md#dot) - [`Circle`](turtle.md#circle) + [`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) - [`Positions and motion`](turtle.md#positions-and-motion) - [`The turtle's own position`](turtle.md#the-turtles-own-position) - [`Many positions at once`](turtle.md#many-positions-at-once) - - [**`ontimer`**](turtle.md#the-game-loop) + [`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) - [`Dialogs`](turtle.md#dialogs) [**`inside`**](turtle.md#detecting-collisions): [`Distance`](turtle.md#distance) diff --git a/docs/libraries/turtle.md b/docs/libraries/turtle.md index 4ee7ccd..3aff302 100644 --- a/docs/libraries/turtle.md +++ b/docs/libraries/turtle.md @@ -153,7 +153,7 @@ color('darkgreen', 'green') # outline, fill register_shape('snake.gif') ``` -#### In a game +#### In a game { data-card-link="skip" } 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()`. @@ -181,7 +181,7 @@ onclick(on_hit) ### Trace movement -#### With tracer +#### With tracer { data-card-link="skip" } The Tracer is whether or not you can see the animation of the turtle moving. @@ -208,7 +208,7 @@ A related but separate setting — `speed(n)` controls how fast each individual circle(50) # drawn instantly, all at once ``` -#### Without tracer +#### Without tracer { data-card-link="skip" } ```python-ref setup(420, 420, 370, 0) @@ -390,7 +390,7 @@ x, y = ball # unpack like any other tuple — 3, 5 aim = Vec2D(x, y) ``` -#### The turtle's own position +#### The turtle's own position { data-card-link="skip" } 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. @@ -406,7 +406,7 @@ setheading(towards(ball)) forward(5) ``` -#### Many positions at once +#### Many positions at once { data-card-link="skip" } A game's state is rarely just one lone position — a trail that grows over time, or several independent entities tracked at once. Both build on the same list/dict operations covered on [Collections](../collections.md). From 4ddc0d7605941aaaea7640d697d3b7c609139a89 Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Wed, 16 Sep 2026 13:55:29 -0700 Subject: [PATCH 10/12] update test to not require home index tags on library cards --- STRUCTURE.md | 9 +++++++-- tests/test_structure.py | 21 +++++++++++++++------ 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/STRUCTURE.md b/STRUCTURE.md index 9769788..970931c 100644 --- a/STRUCTURE.md +++ b/STRUCTURE.md @@ -135,11 +135,16 @@ staying inline.** library-page content (e.g. `libraries/pillow.md`'s per-method sections) — most content pages should never need to go past `###`. -### Homepage keyword deep-links (`index.md`) +### 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. +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 diff --git a/tests/test_structure.py b/tests/test_structure.py index ab4a6e3..be49101 100644 --- a/tests/test_structure.py +++ b/tests/test_structure.py @@ -271,9 +271,9 @@ def test_mkdocs_build_has_no_warnings(built_site): TAG_RE = re.compile(r"<[^>]+>") -def _index_links(): - """page.md -> set of anchors linked from index.md.""" - index_text = (DOCS_DIR / "index.md").read_text() +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: @@ -297,7 +297,14 @@ def test_homepage_keyword_links_cover_all_headings(built_site, path): html_file = _html_path_for(built_site, md_rel) assert html_file.exists(), f"no build output for {md_rel} at {html_file}" - linked = _index_links().get(md_rel, set()) + 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)) @@ -306,9 +313,11 @@ def test_homepage_keyword_links_cover_all_headings(built_site, path): 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 keyword link") + failures.append( + f"{md_rel}#{anchor_id} (h{level} {clean_text!r}) has no {index_md_rel} keyword link" + ) assert not failures, ( - "Every ##/### heading needs its own index.md keyword deep-link (STRUCTURE.md " + 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) From 1126733414800d9da8cff57867bdb2ca62bc9a2a Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Wed, 16 Sep 2026 13:57:21 -0700 Subject: [PATCH 11/12] add missing turtle index tag --- docs/libraries/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/libraries/index.md b/docs/libraries/index.md index 2bc60ab..64f4d07 100644 --- a/docs/libraries/index.md +++ b/docs/libraries/index.md @@ -351,6 +351,7 @@ Libraries allow us to apply Python to real tasks. These are a few popular ones, [`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) From 7b605687700074a7a25a54d3d055691c8f58d47c Mon Sep 17 00:00:00 2001 From: Luka Sherman Date: Wed, 16 Sep 2026 14:57:53 -0700 Subject: [PATCH 12/12] run test synchronously to let state settle first --- tests/test_essentials_toggle.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_essentials_toggle.py b/tests/test_essentials_toggle.py index 129e242..9752dd6 100644 --- a/tests/test_essentials_toggle.py +++ b/tests/test_essentials_toggle.py @@ -121,6 +121,12 @@ def test_link_to_hidden_section_recovers_to_advanced(page, site_url): tuples_link.first.click() + # hashchange (which drives the recovery) always fires as a separate queued + # task, never synchronously with the click — so the reveal can still be + # pending right after .click() returns. Wait for it instead of assuming it + # already happened (this was flaky in CI for exactly that reason). + page.wait_for_function("() => document.getElementById('tuples').hidden === false") + after = page.evaluate( """() => ({ tuplesHidden: document.getElementById('tuples').hidden,