Стандартная библиотека 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-!
без необходимости писать строку документации для этой функции, любой, кто читает ее, уже знает, что функция принимает следующие аргументы.
l
: aList
ofstr
seperator
: astr
и возвращает строку -> 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']
наша функция не требует пояснений и работает так, как описано в аннотациях функций, которые она принимает:
- д: словарь
Dict
- значение:
Any
, что означает, что это может быть что угодно (вам все равно)
и возвращает либо:
- ул. Что является единственным ключом в словаре
- Список[стр]. Список str, как вы узнали раньше
- Буквальный [Нет]. 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
работают рука об руку, но я бы рассказал о дженериках в отдельной статье.