Аннотации при помощи модуля typing

Переменные можно аннотировать типами, которые представляют собой коллекции

numbers: list = []  # переменная numbers хранит список
languages: dict = {}  # переменная languages хранит словарь
temperature: tuple = (1, 2, 3)  # переменная temperature хранит кортеж
letters: set = set('hello')  # переменная letters хранит множество

Такая аннотация допускается, никаких проблем у вас не будет при использовании любой версии Python. Проблемы начнутся, если вы захотите указать аннотацию для элементов коллекции. Например, для numbers логично указать не просто список, а список целых или вещественных чисел. Для такой аннотации вам понадобится модуль typing

Модуль typing

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

 

Модуль typing широко применялся вплоть до версии python3.9 пока не появился стандарт . До версии python3.9 модуль typing был единственным способом проаннотировать составные объекты

 

В модуле хранится множество объектов, которые позволят вам выполнить любую аннотацию.

 

Аннотация коллекций

Вернемся к нашему примеру

numbers: list = []  # переменная numbers хранит список
languages: dict = {}  # переменная languages хранит словарь
temperature: tuple = (1, 2, 3)  # переменная temperature хранит кортеж
letters: set = set('hello')  # переменная letters хранит множество

В модуле typing для каждого типа данных python имеется соответствующий объект. Называться он будет также как встроенный тип данных, только имя начинается с заглавной буквы. Значит, за встроенный тип данных list в модуле typing отвечает объект List.

Для использования любого объекта из модуля typing его необходимо сперва импортировать.

from typing import List, Dict, Tuple, Set

В примере выше мы из модуля typing сразу импортировали четыре объекта: List, Dict, Tuple, Set.  Они перечисляются  через запятую после инструкции from typing import в любом порядке

Давайте перепишем данный код при помощи модуля typing

from typing import List, Dict, Tuple, Set
numbers: List = [1.1, 3.0, 4.5]
languages: Dict = {}
temperature: Tuple = (1, 2, 3)
letters: Set = set('hello')

Теперь мы можем указать для каждой коллекции, какой тип данных должен быть у ее элементов. Вот как это делается:

from typing import List, Set
numbers: List[float] = [1.1, 3.0, 4.5]
letters: Set[str] = set('hello')

Взгляните на аннотацию 

numbers: List[float]

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

По аннотации переменной letters мы понимаем, что в ней хранится множество строк.

letters: Set[str]

Аннотация кортежей и словарей выполняется чуть иначе, рассмотрим их отдельно далее в этой лекции.

 

Объект Union

Во всех примерах аннотации до этого момента мы указывали только один единственный тип данных для переменной или параметра. Но представьте, что у вас есть переменная, которая может принимать значения сразу нескольких типов данных, например, быть целым или вещественным числом, а также сохранять логический тип. Если у вас возникает такая ситуация, вам на помощь может прийти объект Union. Его необходимо импортировать из модуля typing

from typing import Union

C помощью объекта Union можно указывать сразу несколько типов данных, которые могут быть сохранены в переменную. Для этого необходимо в квадратных скобках перечислить через запятую необходимые типы данных. Ниже представлен пример использования Union

param: Union[int, float, bool]

При такой аннотации в переменной param могут храниться вещественные и целые числа, а также логические значения True и False.

Вот пример использования объекта при аннотации параметров функции и возвращаемого значения

from typing import Union
def add_numbers(a: Union[int, float],
                b: Union[int, float]) -> Union[int, float]:
    return a + b
print(add_numbers.__annotations__)
print(add_numbers(10, 25.7))
print(add_numbers(10, 25))

В примере выше мы говорим, что входные параметры и возвращаемый результат могут быть целыми и вещественными числами. 

Но если подумать, то операцию сложения можно производить также и над списками, строками, кортежами, типом bool. Если добавлять все перечисленные типы в аннотацию, получится очень громоздкая запись, которая не умещается в одну строку. Вот пример реализации такой функции, которая выполняет сложение с произвольным объектом

from typing import Union
def add(a: Union[int, float, str, list, bool],
        b: Union[int, float, str, list, bool])\
        -> Union[int, float, str, list, bool]:
    return a + b
print(add.__annotations__)
print(add(10, 25.7))
print(add('hello', 'world'))
print(add([1, 2], [3, 4]))
print(add(True,True))

В случаях, когда в аннотацию входят практически все встроенные объекты, можно подумать об использовании объекта Any.

Union полезна в версиях python до 3.10, поскольку в этой версии появилась короткая запись данной функции через вертикальную черту:

param: int| float| str

Объект Optional

Давайте вновь взглянем с вами на функцию append_to_list.

def append_to_list(value, my_list=None):
    if my_list is None:
        my_list = []
    my_list.append(value)
    return my_list

Мы разбирали ее на уроке «». Здесь вам стоит обратить внимание на параметр my_list. По умолчанию он принимает значение None, но в него также может быть передан список. Следовательно, всего имеется два возможных типа данных у параметра my_list. Его можно проаннотировать при помощи объекта Union

def append_to_list(value, my_list: Union[list, None] = None):
    if my_list is None:
        my_list = []
    my_list.append(value)
    return my_list

Либо можно воспользоваться специальным объектом Optional , который специально создан для аннотирования указанного типа и значения None. Не забудьте перед использованием импортировать объект Optional

from typing import Optional

Вот взгляните на примеры аннотации

from typing import Optional
num: Optional[int] = None
word: Optional[str] = None

Переменная num из примера выше может теперь хранить в себе целые числа и также значение None. Переменная wordпроаннотирована строковым типом и еще пустым значением None. Обязательно после Optional тип указывается в квадратных скобках.

Инструкция Optional[int] эквивалента записи Union[None, int], а запись Optional[str] равнозначна записи Union[None, str].

 

Если не указать Optional у переменной word, то при попытке сохранить значение None IDE будет выдавать предупреждение о недопустимом типе данных.

Познакомившись с объектом Optional, давайте перепишем функцию append_to_list.

from typing import Optional
def append_to_list(value, my_list: Optional[list] = None):
    if my_list is None:
        my_list = []
    my_list.append(value)
    return my_list

 

Объект Any

Например, вы хотите указать при помощи аннотаций, что в переменной можно сохранить любой тип данных. В этом вам поможет объект Any из модуля typing

from typing import Any
value: Any
value = 10
print(value)
value = [1, 2, 3]
print(value)
value = {'hi': 'привет'}
print(value)

Все в той же функции append_to_list у нас имеется параметр value, значение которого добавляется в список.

from typing import Optional
def append_to_list(value, my_list: Optional[list] = None):
    if my_list is None:
        my_list = []
    my_list.append(value)
    return my_list

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

from typing import Optional, Any, List
def append_to_list(value: Any, my_list: Optional[list] = None) -> List[Any]:
    if my_list is None:
        my_list = []
    my_list.append(value)
    return my_list

 

Аннотация элементов кортежа

Помните, как мы аннотировали элементы списка?

from typing import List
numbers: List[float] = [1.1, 3.0, 4.5]

Тут мы указали тип float для всех элементов списка. С кортежами такая схема не пройдет, потому что они, в отличие от списков, часто используются для разнотипных элементов.

Для кортежа в квадратных скобках нужно указывать тип каждого элемента по отдельности

from typing import Tuple
words: Tuple[str, int] = ("hello", 300)

Если же планируется использовать кортеж аналогично списку: хранить неизвестное количество однотипных элементов, можно воспользоваться многоточием (...).


from typing import Tuple
words: Tuple[str, ...] = ("hello", "world", '!')

При такой схеме мы указываем сразу всем элементам кортежа один тип данных str.

 

Аннотация словарей

Чтобы добавить аннотацию типа для словаря,  необходимо использовать объект Dict модуля typing, и далее указать тип ключа и тип значения следующим образом:

from typing import Dict
Dict[key_type, value_type]

Например, в словаре person и ключ, и значение представлены в виде строки:

person = { "first_name": "Джон", "last_name": "Доу"}

Вы можете аннотировать его следующим образом:

from typing import Dict
person: Dict[str, str] = { "first_name": "John", "last_name": "Doe"}

Тип Dict указывает, что ключи словаря person имеют тип str, а значения имеют тип str.

Давайте взглянем на более сложный пример

from typing import Dict, Optional, Union
def foo(bar: Dict[Union[str, int], Optional[str]]) -> bool:
   return True

Здесь функция foo принимает один аргумент bar, он должен являться словарем, у которого ключи могут быть либо строкой либо целым числом, а значения могут быть либо пустыми (тип None) , либо строкой 

Перейти к следующему шагу

Комментарии