Skip to content

Commit a7f9ed9

Browse files
committed
trim down type annotation section
1 parent 3c84f8a commit a7f9ed9

2 files changed

Lines changed: 35 additions & 73 deletions

File tree

episodes/22-scaling-up-unit-testing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ and allows others to verify against correct behaviour.
354354
## Optional exercises
355355

356356
Checkout
357-
[these optional exercises](25-section2-optional-exercises.md)
357+
[these optional exercises](26-section2-optional-exercises.md)
358358
to learn more about code coverage.
359359

360360

episodes/25-type-annotation.md

Lines changed: 34 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ exercises: 45
1010
- List the most important type checkers
1111
- Apply type annotations to simple functions
1212
- Read parametric types like `list[int]` or `set[str]`
13-
- Use type unions to introduce flexibility
14-
- Design data classes using type annotations
15-
- Understand the complexities of going all-out on types
13+
- Understand the use of type annotations in libraries like `pydantic`, `cattrs` or `msgspec`.
1614

1715
::::::::::::::::::::::::::::::::::::::::::::::::::
1816

@@ -29,6 +27,7 @@ exercises: 45
2927
- Force you to handle edge cases.
3028
- Have documentation that is always correct.
3129
- Better autocompletion.
30+
- Automatic serialization.
3231

3332
## Tools
3433

@@ -65,82 +64,17 @@ What does the error message say went wrong? Do you think this is a good message?
6564
The error says we can't multiply sequences with a non-int of type float and points to the sub-expression `r * x`. However, the mistake is made at the call point by entering a `str` argument in the first place.
6665
::::
6766

68-
How about the following code?
69-
70-
```python
71-
def binary_search(lst, value):
72-
low = 0
73-
high = len(lst)-1
74-
while low <= high:
75-
mid = (low+high) / 2
76-
if lst[mid] > value:
77-
high = mid-1
78-
elif lst[mid] < value:
79-
low = mid+1
80-
else:
81-
return mid
82-
return -1
83-
84-
binary_search([3.4, 7.8, 9.1], 5.2)
85-
```
86-
87-
Run a type-checker on the binary search example. What does it say?
88-
89-
:::: solution
90-
We can't index `lst` with a floating point value. The mistake is at the division by 2, we should have used the `//` operator.
91-
::::
92-
93-
Add type annotation to these codes:
67+
Change the function signature to:
9468

9569
```python
9670
def logistic_map(r: float, x: float) -> float:
9771
return r * x * (1 - x)
9872
```
9973

100-
Do we get nicer messages now? How about our binary search? We're not restricted to typing function arguments (although that's the most common use). By explicitly typing intermediate values, we may catch errors early.
101-
102-
```python
103-
def binary_search(lst: list, value) -> int:
104-
low: int = 0
105-
high: int = len(lst)-1
106-
while low <= high:
107-
mid: int = (low+high) / 2
108-
if lst[mid] > value:
109-
high = mid-1
110-
elif lst[mid] < value:
111-
low = mid+1
112-
else:
113-
return mid
114-
return -1
115-
```
116-
117-
Is the problem now identified with `mypy`? Note that we haven't typed `value` yet: we'll get to that later.
118-
119-
:::: solution
120-
In the `logistic_map` example we find that type hinting the arguments helps us identify that the caller made a mistake.
121-
Only by specifying that we expect `mid` to be an integer, is the true culprit revealed.
122-
::::
123-
74+
Do you see any effect in on the erronous call in your editor?
12475
:::
12576

126-
## Union types
127-
128-
Sometimes we don't know the exact type of a value, or we'd lik to specify that a function can handle multiple different types. This is where type unions come in. For example, in the binary search, it is nicer to return `None` when we don't find an item. We can use the `|` operator to create a **type union**.
129-
130-
```python
131-
def binary_search(lst: list, value) -> int | None:
132-
low: int = 0
133-
high: int = len(lst)-1
134-
while low <= high:
135-
mid: int = (low+high) // 2
136-
if lst[mid] > value:
137-
high = mid-1
138-
elif lst[mid] < value:
139-
low = mid+1
140-
else:
141-
return mid
142-
return None
143-
```
77+
### Abstract types
14478

14579
We don't always care about the precise type of an object. For instance, if we just want to write a for loop over an iterable, and sometimes we want to express that `Any` object will do:
14680

@@ -153,14 +87,27 @@ def print_numbered_list(items: Iterable[Any]):
15387
print(i, v)
15488
```
15589

90+
There are many abstract types available in `collections.abc`.
91+
92+
### Completion
93+
94+
Write a function that changes all commas to semi-colons. Start by entering the following:
95+
96+
```python
97+
def semicolonize(s: str) -> str:
98+
return s
99+
```
100+
101+
Type a `.` after the `s`. Can you see the completion?
102+
156103
## Data classes
157104

158105
::: info
159106
### Data before classes
160107
In many languages structures or records are considered more primitive than classes, not so in Python. We will learn more about classes and their place in software design in part 3. In this section we'll only consider data classes as a means of grouping data.
161108
:::
162109

163-
Type annotations go really well together with data classes, a means of combining elements into a larger data structure.
110+
Type annotations go really well together with data classes, a means of combining elements into a larger data structure. Python supports creating classes using type annotation like so:
164111

165112
```python
166113
from dataclasses import dataclass
@@ -176,6 +123,8 @@ address = Address("Science Park", 402, "Matrix THREE")
176123
print(f"{address.street} {address.number}")
177124
```
178125

126+
Now you don't need to define an `__init__` method. There are nice packages that use this technique to allow automatic serialisation and deserialisation. Check out the [`msgspec` package](https://jcristharif.com/msgspec/index.html).
127+
179128
::: challenge
180129
### Autocompletion
181130

@@ -191,6 +140,19 @@ When you use type-annotation, you'll have better auto-completion.
191140
::::
192141
:::
193142

143+
::: challenge
144+
### Serialization
145+
146+
Install `msgspec` and try writing and reading back an `Address` object to JSON. Can you think of the advantages of using this approach over Python native `json.dump`?
147+
148+
:::: solution
149+
- less code
150+
- automatic validation
151+
- user friendly error reporting
152+
- high performance
153+
::::
154+
:::
155+
194156
## Optional: Generics and protocols
195157

196158
How would we type a function that returns the first element in a list? Suppose that we know that the list contains integers. Then:

0 commit comments

Comments
 (0)