Skip to content

Commit 210a17f

Browse files
committed
feat: more examples of typing
Signed-off-by: Henry Schreiner <[email protected]>
1 parent 476de15 commit 210a17f

File tree

2 files changed

+148
-0
lines changed

2 files changed

+148
-0
lines changed

content/week08_static_typing/typing.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,14 @@ also allowed? If nothing is allowed except `B`, your TypeVar is **invariant**
10371037
**covariant** and `*_co` is recommended for the name. If you allow parents, then
10381038
it is **contravariant**, and `*_contra` is recommended for the name.
10391039

1040+
Why? It turns out, if you have a read-only values, covariant variables are safe;
1041+
when you read a variable, children act like their parents. That's why immutable
1042+
protocols, like `Sequence` and `Mapping`, are covariant, while the mutable ones,
1043+
like `MutableMapping`, as well as list, are not. If you have write-only values,
1044+
then you can always write a parent, so contravariant is fine for write-only
1045+
usage (say in a method call; write-only collections are somewhat rare!). If you
1046+
read and write, then it is invariant.
1047+
10401048
Unions are covariant. `B | None` would also accept `C`.
10411049

10421050
Lists (generally anything mutable) are invariant. If you have a `list[B]`, it is

slides/week-08-1.md

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,3 +543,143 @@ Try `mypyc`; it can compile your Python, giving a 2-5x speedup on a fully typed
543543
- [Awesome Python Typing](https://github.com/typeddjango/awesome-python-typing): A curated list of links to Python typing related things
544544
- [Adam Johnson's Typing series](https://adamj.eu/tech/tag/mypy/)
545545
- [MyPy's cheat sheet](https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html)
546+
547+
---
548+
549+
## Examples
550+
551+
Following are some examples.
552+
553+
---
554+
555+
## What's the best annotation for this function?
556+
557+
```python
558+
def all_upper(values):
559+
return [v.upper() for v in values]
560+
561+
562+
items = ["one", "two"]
563+
result = all_upper(items)
564+
```
565+
566+
---
567+
568+
## Attempt 1: explicit values
569+
570+
```python
571+
def all_upper(values: list[str]) -> list[str]:
572+
return [v.upper() for v in values]
573+
574+
575+
items = ["one", "two"]
576+
result = all_upper(items)
577+
```
578+
579+
---
580+
581+
## What about different container?
582+
583+
```python
584+
def all_upper(values: list[str]) -> list[str]:
585+
return [v.upper() for v in values]
586+
587+
588+
items = ("one", "two")
589+
result = all_upper(items) #
590+
```
591+
592+
---
593+
594+
## Attempt 2: Protocol
595+
596+
```python
597+
def all_upper(values: Sequence[str]) -> list[str]:
598+
return [v.upper() for v in values]
599+
600+
601+
items = ("one", "two")
602+
result = all_upper(items)
603+
```
604+
605+
---
606+
607+
## Details
608+
609+
This is fairly common: Use a Protocol for input arguments, and be as exact as possible on output arguments!
610+
611+
In fact, _try never to return a Protocol_. You want the type system to track _actual_ types.
612+
613+
---
614+
615+
## Bad use of Protocol
616+
617+
```python
618+
from collections.abc import Sequence
619+
from typing import Any, reveal_type
620+
import copy
621+
622+
623+
def copy_sequence(seq: Sequence[Any]) -> Sequence[Any]:
624+
return copy.copy(seq)
625+
626+
627+
a = ["1", "2"]
628+
b = copy_sequence(a)
629+
reveal_type(b) # Sequence[Any]
630+
```
631+
632+
As a reader, the type of `b` is obvious: it's a `list[str]`. But typing can't see that.
633+
634+
---
635+
636+
## Better
637+
638+
```python
639+
from collections.abc import Sequence
640+
from typing import Any, reveal_type
641+
import copy
642+
643+
644+
def copy_sequence[T: Sequence[Any]](seq: T) -> T:
645+
return copy.copy(seq)
646+
647+
648+
a = ["1", "2"]
649+
b = copy_sequence(a)
650+
reveal_type(b) # list[str]
651+
```
652+
653+
---
654+
655+
## Smarter return types
656+
657+
You want the return type to be as accurate as possible. Use overloads if the type depends on the arguments in a non-trivial way (not passed through).
658+
659+
```python
660+
def call(*args: str, quiet: bool = False) -> str | None:
661+
# Logic would go here
662+
if quiet:
663+
return f"result of {args}"
664+
return None
665+
666+
667+
call("echo", "hi", quiet=True).strip() # ❌ Can't call on None
668+
```
669+
670+
---
671+
672+
## Smarter return types
673+
674+
```python
675+
@overload
676+
def call(*args: str, quiet: Literal[False] = ...) -> None: ...
677+
678+
679+
@overload
680+
def call(*args: str, quiet: Literal[True]) -> str: ...
681+
682+
683+
def call(*args: str, quiet: bool = False) -> str | None:
684+
"See above"
685+
```

0 commit comments

Comments
 (0)