Skip to content

Commit 4cbe7cd

Browse files
committed
Add Protocols example
1 parent f7cbe14 commit 4cbe7cd

8 files changed

Lines changed: 164 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ The format is inspired by [Keep a Changelog](https://keepachangelog.com/en/1.1.0
2525
- `iterators` and `generator-expressions` examples to make the Iteration arc explicit.
2626
- Syntax coverage examples for loop control, loop `else`, assertions, deletion, scope rebinding, positional-only parameters, assignment expressions, `yield from`, exception chaining/groups, advanced match patterns, inheritance, metaclasses, async iteration/context managers, import aliases, and specialized operators/literals.
2727
- Optional `see_also` example graph links with validation tests.
28+
- `Protocol` example covering structural typing and its boundary with inheritance.
2829

2930
### Changed
3031

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Production: <https://www.pythonbyexample.dev> (`workers.dev` remains enabled as
66

77
## Features
88

9-
- 69 curated Python 3.13 examples in learning order
9+
- 70 curated Python 3.13 examples in learning order
1010
- Literate source/output cells for each example walkthrough
1111
- Editable complete examples powered by CodeMirror
1212
- Read-only syntax highlighting powered by Shiki

src/asset_manifest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# Generated by scripts/fingerprint_assets.py. Do not edit by hand.
22
ASSET_PATHS = {'SITE_CSS': '/site.06e133dcde6a.css', 'SYNTAX_JS': '/syntax-highlight.3b6c7f730d46.js', 'EDITOR_JS': '/editor.dd81f5171b14.js'}
3-
HTML_CACHE_VERSION = '6dd0b780fadd'
3+
HTML_CACHE_VERSION = '002f150145c0'

src/example_sources/manifest.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ order = [
6363
"modules",
6464
"import-aliases",
6565
"type-hints",
66+
"protocols",
6667
"enums",
6768
"regular-expressions",
6869
"number-parsing",

src/example_sources/protocols.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
+++
2+
slug = "protocols"
3+
title = "Protocols"
4+
section = "Types"
5+
summary = "Protocol describes required behavior for structural typing."
6+
doc_path = "/library/typing.html#typing.Protocol"
7+
see_also = [
8+
"type-hints",
9+
"classes",
10+
"inheritance-and-super",
11+
]
12+
+++
13+
14+
`Protocol` describes the methods or attributes an object must provide. It exists for structural typing: if an object has the right shape, type checkers can treat it as compatible.
15+
16+
This is different from inheritance. Inheritance says a class is explicitly derived from a parent; a protocol says callers only need a particular behavior.
17+
18+
At runtime, ordinary method lookup still applies. Protocols are mainly for static analysis, documentation, and API boundaries.
19+
20+
:::program
21+
```python
22+
from typing import Protocol
23+
24+
class Greeter(Protocol):
25+
def greet(self) -> str:
26+
...
27+
28+
class Person:
29+
def __init__(self, name):
30+
self.name = name
31+
32+
def greet(self):
33+
return f"hello {self.name}"
34+
35+
36+
def welcome(greeter: Greeter):
37+
print(greeter.greet())
38+
39+
welcome(Person("Ada"))
40+
print(Greeter.__name__)
41+
```
42+
:::
43+
44+
:::cell
45+
A protocol names required behavior. The ellipsis marks the method body as intentionally unspecified, similar to an interface declaration.
46+
47+
```python
48+
from typing import Protocol
49+
50+
class Greeter(Protocol):
51+
def greet(self) -> str:
52+
...
53+
54+
print(Greeter.__name__)
55+
```
56+
57+
```output
58+
Greeter
59+
```
60+
:::
61+
62+
:::cell
63+
A class can satisfy the protocol without inheriting from it. `Person` has a compatible `greet()` method, so it has the right shape for static type checkers.
64+
65+
```python
66+
class Person:
67+
def __init__(self, name):
68+
self.name = name
69+
70+
def greet(self):
71+
return f"hello {self.name}"
72+
73+
print(Person("Ada").greet())
74+
```
75+
76+
```output
77+
hello Ada
78+
```
79+
:::
80+
81+
:::cell
82+
Use the protocol as an annotation at the API boundary. The function only cares that the object can greet; it does not care about the concrete class.
83+
84+
```python
85+
def welcome(greeter: Greeter):
86+
print(greeter.greet())
87+
88+
welcome(Person("Ada"))
89+
```
90+
91+
```output
92+
hello Ada
93+
```
94+
:::
95+
96+
:::note
97+
- Protocols are for structural typing: compatibility by shape rather than explicit inheritance.
98+
- Type checkers understand protocols; normal runtime method calls still do the work.
99+
- Prefer inheritance when shared implementation matters, and protocols when only required behavior matters.
100+
:::

src/example_sources_data.py

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

tests/fixtures/golden_examples.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2526,6 +2526,63 @@
25262526
'code': 'def label(score: int) -> str:\n return f"score={score}"\n\nprint(label("high"))',
25272527
'output': 'score=high',
25282528
'line': 44}]},
2529+
{'slug': 'protocols',
2530+
'title': 'Protocols',
2531+
'section': 'Types',
2532+
'summary': 'Protocol describes required behavior for structural typing.',
2533+
'doc_url': 'https://docs.python.org/3.13/library/typing.html#typing.Protocol',
2534+
'code': 'from typing import Protocol\n'
2535+
'\n'
2536+
'class Greeter(Protocol):\n'
2537+
' def greet(self) -> str:\n'
2538+
' ...\n'
2539+
'\n'
2540+
'class Person:\n'
2541+
' def __init__(self, name):\n'
2542+
' self.name = name\n'
2543+
'\n'
2544+
' def greet(self):\n'
2545+
' return f"hello {self.name}"\n'
2546+
'\n'
2547+
'\n'
2548+
'def welcome(greeter: Greeter):\n'
2549+
' print(greeter.greet())\n'
2550+
'\n'
2551+
'welcome(Person("Ada"))\n'
2552+
'print(Greeter.__name__)\n',
2553+
'expected_output': 'hello Ada\nGreeter\n',
2554+
'notes': ['Protocols are for structural typing: compatibility by shape rather than explicit inheritance.',
2555+
'Type checkers understand protocols; normal runtime method calls still do the work.',
2556+
'Prefer inheritance when shared implementation matters, and protocols when only required behavior '
2557+
'matters.'],
2558+
'cells': [{'prose': ['A protocol names required behavior. The ellipsis marks the method body as intentionally '
2559+
'unspecified, similar to an interface declaration.'],
2560+
'code': 'from typing import Protocol\n'
2561+
'\n'
2562+
'class Greeter(Protocol):\n'
2563+
' def greet(self) -> str:\n'
2564+
' ...\n'
2565+
'\n'
2566+
'print(Greeter.__name__)',
2567+
'output': 'Greeter',
2568+
'line': 22},
2569+
{'prose': ['A class can satisfy the protocol without inheriting from it. `Person` has a compatible '
2570+
'`greet()` method, so it has the right shape for static type checkers.'],
2571+
'code': 'class Person:\n'
2572+
' def __init__(self, name):\n'
2573+
' self.name = name\n'
2574+
'\n'
2575+
' def greet(self):\n'
2576+
' return f"hello {self.name}"\n'
2577+
'\n'
2578+
'print(Person("Ada").greet())',
2579+
'output': 'hello Ada',
2580+
'line': 40},
2581+
{'prose': ['Use the protocol as an annotation at the API boundary. The function only cares that the object '
2582+
'can greet; it does not care about the concrete class.'],
2583+
'code': 'def welcome(greeter: Greeter):\n print(greeter.greet())\n\nwelcome(Person("Ada"))',
2584+
'output': 'hello Ada',
2585+
'line': 59}]},
25292586
{'slug': 'enums',
25302587
'title': 'Enums',
25312588
'section': 'Types',

tests/test_app.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ def test_language_surface_has_good_initial_coverage(self):
112112
"inheritance-and-super",
113113
"metaclasses",
114114
"type-hints",
115+
"protocols",
115116
"enums",
116117
"json",
117118
"assertions",
@@ -177,6 +178,7 @@ def test_syntax_surface_has_explicit_examples(self):
177178
"pattern = r\"\\d+\"",
178179
"data = b\"py\"",
179180
"number = 2 + 3j",
181+
"class Greeter(Protocol):",
180182
"print(Scale(2) @ Scale(3))",
181183
"print(...)",
182184
]

0 commit comments

Comments
 (0)