We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Precise Type Hints with Literals and Generics step 23 of 26
Rewrite Old Typing Spellings Such as List and Optional
You inherit a codebase written for Python 3.8. Its hints look like
Dict[str, List[int]] and Optional[str], and every file starts with a long
from typing import ... line. Since Python 3.9 you can write list[int],
and since 3.10 you can write str | None. Tools such as Ruff's UP rules
make this change for you. In this exercise you build a small version of one.
It rewrites annotation text and works out which imports are still needed.
assert modernise(["Dict[str, List[int]]", "Sequence[Any]"]) == (
["dict[str, list[int]]", "Sequence[Any]"],
["from collections.abc import Sequence", "from typing import Any"],
)
What to write
modernise(annotations: list[str]) -> tuple[list[str], list[str]] returns the
rewritten annotations in input order, and the import lines they need.
| Old name or form | New form | Import from |
|---|---|---|
List, Dict, Tuple, Set, FrozenSet, Type |
list, dict, tuple, set, frozenset, type |
nothing |
Optional[X] |
X | None |
nothing |
Union[A, B] |
A | B |
nothing |
Callable, Sequence, Iterable, Iterator, Mapping, MutableMapping, MutableSequence, Hashable, Awaitable, Coroutine, Generator |
the same name | collections.abc |
Any, Final, ClassVar, Literal, Never, NoReturn, Self |
the same name | typing |
any other name, such as int or MyModel |
the same name | nothing |
The supplied BUILTIN, ABC_NAMES and TYPING_NAMES constants hold these
names. Other names are already imported, so add no import for them.
- Recurse. Rewrite the arguments inside brackets too.
- Flatten unions. Nested
Union,Optionaland|become one flat union. Then drop repeated members, keeping the first:Union[int, Optional[str]]becomesint | str | None.Optional[Optional[str]]becomesstr | None.Union[int, int]becomesint.List[int] | Optional[str]becomeslist[int] | str | None.
- Keep
Callable's parameter list. InCallable[[int, str], X], keep the inner brackets and rewrite what is inside them. - Keep
..., as inCallable[..., None]orTuple[int, ...]. - Copy
Literalvalues exactly. InLiteral["a,b", "x|y", "List[int]"], the quoted strings are data. Do not change them, and do not add imports for words inside them. - Format separators as
,and|, and ignore spaces around each expression and argument. - Imports. Write one line per module,
from <module> import <names>. Sort the lines by module name, and sort the names inside each line. Each name appears once across the whole input. With no imports, return[]. - An empty input returns
([], []).
The input
Every input is a well-formed annotation built from plain names, square
brackets, Callable parameter lists, ..., unions and Literal values.
Strings use single or double quotes and may contain backslash escapes. You do
not need to handle parentheses, comments, dotted names such as
typing.List, string prefixes, triple quotes or quoted forward references.
Use the supplied split_top(text, separator) to split on commas and pipes.
It ignores separators inside brackets and quoted strings. Do not eval the
text.
Annotate your helpers fully. For example, an accumulator for imports is a
dict[str, set[str]], not a bare dict.
Going further
mypy accepts the old spellings, so this rewrite is a style change, not a type
fix. Optional[X] means a value may be None. It does not make an argument
optional. An argument is optional only when its parameter has a default. A
real tool also has to handle aliased imports, shadowed names and libraries that read
annotations at runtime. Ruff's UP006
and UP045
rules do this on whole files.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Python reference solution
Sign in to track your attempts toward unlocking the reference solution.