Стандартная библиотека Python предоставляет буквально все типы утилит, которые могут вам когда-либо понадобиться.

Таким образом, вы можете начать создавать подсказки сложных типов, используя ванильный Python. Рассмотрим пару практических примеров

from typing import List, Dict, Union, Literal, Any
# imagine you have a function that accepts a list of strings and joins them using a seperator
# you can easily make your function's purpose obvious by doing
def join(l: List[str], seperator: str = '-') -> str:
    return seperator.join(l)
In [1]:print(join(['type', 'annotations', 'make', 'coding', 'easy', '!']))
type-annotations-make-coding-easy-!

без необходимости писать строку документации для этой функции, любой, кто читает ее, уже знает, что функция принимает следующие аргументы.

  1. l: a List of str
  2. seperator: a str

и возвращает строку -> str

Поэкспериментируем немного дальше…

# you have a function that accepts a dictionary, a value,
# and returns a key or list of keys whose value in the dictionary matches value
# but if none matches, returns None
# let me explain using annotations
def find_keys_that_match_value(d: Dict, value: Any) -> Union[str, List[str], Literal[None]]:
matches = []
for k, v in d.items():
# iterating over the keys(k) and values(v) in d(dictionary supplied to the function)
if v == value: # check if v matches value(the value supplied to the function)
matches.append(k) # add it to the list if it matches
if len(matches) == 0:
# that means no match was found so return None
return None
elif len(matches) == 1:
# that means only one match was found so return it
return matches[0]
else:
# means there were more than one match found
return matches
# let us test our function
no_match = {}
one_match = {'first': 'foo'}
two_matches = {'first': 'foo', 'second': 'foo'}
three_matches = {'first': 'foo', 'second': 'foo', 'third': 'foo'}
In [1]: print(find_keys_that_match_value(no_match, 'foo'))
None
In [2]: print(find_keys_that_match_value(one_match, 'foo'))
first
In [3]: print(find_keys_that_match_value(two_matches, 'foo'))
['first', 'second']
In [4]: print(find_keys_that_match_value(three_matches, 'foo'))
['first', 'second', 'third']

наша функция не требует пояснений и работает так, как описано в аннотациях функций, которые она принимает:

  1. д: словарь Dict
  2. значение: Any, что означает, что это может быть что угодно (вам все равно)

и возвращает либо:

  1. ул. Что является единственным ключом в словаре
  2. Список[стр]. Список str, как вы узнали раньше
  3. Буквальный [Нет]. Literal[‘a’] – это то же самое, что и обычное или простое ‘a’.

-> Union[str, List[str], Literal[None]]

Union[a,b] is same as a or b

Аннотация очень ЭЛЕГАНТНАЯ и ЧИСТАЯ, но в то же время мы делаем очень большое предположение — ключи в словаре будут строками. Это почти всегда верно, но словари Python принимают любой хешируемый объект в качестве ключей, например: int, str, float, tuple... Так что в python правильно делать {1:'foo',2:'foo'}.

Поскольку мы предполагаем, что ключи будут строками, мы также предполагаем, что функция вернет либо None, либо str, либо список строк List[str]. Python предвидел эту проблему, поэтому они предоставили ее нам TypeVar. Нам просто нужно внести несколько изменений в нашу почти идеальную функцию.

from typing import TypeVar
k = TypeVar('k') # this would represent every key in the dictionary
v = TypeVar('v') # this would represent every value in the dictionary
def find_keys_that_match_value(d: Dict[k,v], value: Any) -> Union[k, List[k], Literal[None]]:
    ...
# THAT'S ALL WE HAVE TO DO

теперь функция говорит что-то вроде этого:

Я принимаю

"d" a dictionary which "k" would represent the keys in it while "v" would represent the values in it.

Я также принимаю

"value" which could be anything

но, что более важно, я возвращаюсь либо

a single "k" from the dictionary I accepted, a list of "k" from the dictionary I accepted or None

Похлопайте себя по спине, вы сделали это. Теперь у вас идеальная функция с идеальной аннотацией. Если вы проверите сигнатуру вашей функции, она будет выглядеть так:

(d: Dict[~k, ~v], value: Any) -> Union[~k, List[~k], Literal[None]]

что объяснимо любому, кто использует вашу суперполезную функцию. Но что, если вы хотите написать класс, который ведет себя примерно так же, как эта функция? Python предвидел это, поэтому они дали нам Generic. TypeVar и Generic работают рука об руку, но я бы рассказал о дженериках в отдельной статье.