Typing

Компонент стандартной библиотеки, используемый для [type-annotation] сложных объектов. Используется в [mypy]

Ссылка на документацию

Основные принципы

Aliaces

Псевдонимы служат для сокращения аннотирующих конструкций

Vector = list[float]

def scale(scalar: float, vector: Vector) -> Vector:
    return [scalar * num for num in vector]

New Type

Специальная обертка, необходимая когда требуется новый тип, без затрат на создание класса

from typing import NewType

UserId = NewType('UserId', int)

def get_user(x: UserId): ...

get_user(UserId(123456)) # this is fine
get_user(123456) # that's an int, not a UserId

UserId(123456) + 123456 # fine, because UserId is a subclass of int

Смотри подробнее на оверфло

Callable

Аннотация вызываемых объектов

Callable[[Arg1Type, Arg2Type], ReturnType] или так Callable[..., ReturnType]

Вызываемые объекты, которые принимают другие вызываемые объекты в качестве аргументов, могут указывать, что их типы параметров зависят друг от друга, используя ParamSpec. Кроме того, если этот вызываемый объект добавляет или удаляет аргументы из других вызываемых объектов, может использоваться оператор Concatenate. Они принимают форму Callable[ParamSpecVariable, ReturnType] и Callable[Concatenate[Arg1Type, Arg2Type, ..., ParamSpecVariable], ReturnType] соответственно.

Generics

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

from collections.abc import Mapping, Sequence

def notify_by_email(employees: Sequence[Employee],
                    overrides: Mapping[str, str]) -> None: ...

Пользователи могут определять собственные дженерики, наследуясь от Generic с помощью TypeVar

TypeVar

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

У TypeVar есть еще несколько вариантов. Вы можете ограничить возможные значения, передав больше типов (например, TypeVar(name, int, str)), или вы можете указать граничное состояние, чтобы каждое значение переменной типа было подтипом этого типа (например, TypeVar(name, bound=int)). Кроме того, вы можете решить, является ли переменная типа ковариантной, контравариантной или ни той, ни другой при ее объявлении. По сути, это решает, когда подклассы или суперклассы могут использоваться вместо универсального типа.

Без TypeVar не было бы хорошего способа реализовать следующий пример

from typing import TypeVar, Generic, List, Tuple

# Two type variables, named T and R
T = TypeVar('T')
R = TypeVar('R')

# Put in a list of Ts and get out one T
def get_one(x: List[T]) -> T: ...

# Put in a T and an R, get back an R and a T
def swap(x: T, y: R) -> Tuple[R, T]:
    return y, x

# A simple generic class that holds a value of type T
class ValueHolder(Generic[T]):
    def __init__(self, value: T):
        self.value = value
    def get(self) -> T:
        return self.value

x: ValueHolder[int] = ValueHolder(123)
y: ValueHolder[str] = ValueHolder('abc')

Типичный пример

T = TypeVar('T')  # Can be anything
A = TypeVar('A', str, bytes)  # Must be str or bytes

The Any type

Особым типом является Any. Средство проверки статического типа будет рассматривать каждый тип как совместимый с Any, а Any — как совместимый со всеми типами.

Duck typing

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

Специальные типы и примитивы

  • Any
  • NoReturn специальный тип, указывающий, что функция никогда не возвращает значение
from typing import NoReturn

def stop() -> NoReturn:
    raise RuntimeError('no way')
  • TypeAlias Специальная аннотация для явного объявления алиаса
from typing import TypeAlias

Factors: TypeAlias = list[int]
  • Tuple (Tuple[int, float, str], Tuple[()], Tuple[int, ...].)
  • Union эквивалент X | Y (последний вариант рекомендуем)

Нюансы реализации:

Union[Union[int, str], float] == Union[int, str, float]
Union[int] == int
Union[int, str, int] == Union[int, str] == int | str
Union[int, str] == Union[str, int]
  • Optional эквивалент X | None или Union[X, None]
  • Callable
  • Concatenate
  • Type(Generic[CT_co]) Переменная, аннотированная с помощью C, может принимать значение типа C.
a = 3         # Has type 'int'
b = int       # Has type 'Type[int]'
c = type(a)   # Also has type 'Type[int]'
  • Literal указывает на один или несколько литералов
def validate_simple(data: Any) -> Literal[True]:  # always returns True
    ...

MODE = Literal['r', 'rb', 'w', 'wb']
def open_helper(file: str, mode: MODE) -> str:
    ...

open_helper('/some/path', 'r')  # Passes type check
open_helper('/other/path', 'typo')  # Error in type checker
  • ClassVar Разметка переменных класса
class Starship:
    stats: ClassVar[dict[str, int]] = {} # class variable
    damage: int = 10                     # instance variable
  • Final указывает на то. что переменная не может быть переопределена в сабклассах
MAX_SIZE: Final = 9000
MAX_SIZE += 1  # Error reported by type checker

class Connection:
    TIMEOUT: Final[int] = 10

class FastConnector(Connection):
    TIMEOUT = 1  # Error reported by type checker
  • Annotated анотация с помощью метаданных, например так: Annotated[int, ValueRange(3, 10), ctype("char")]
  • TypeGuard Используется для аннотации возвращаемого типа с помощью определяемой пользователем guard функции, когда тип нельзя установить корректно на этапе объявления. TypeGuard принимает только один аргумент. Во время выполнения функции, помеченные таким образом, должны возвращать логическое значение. Добавлен в 3.10
def is_str_list(val: List[object]) -> TypeGuard[List[str]]:
    '''Determines whether all objects in the list are strings'''
    return all(isinstance(x, str) for x in val)

def func1(val: List[object]):
    if is_str_list(val):
        # Type of ``val`` is narrowed to ``List[str]``.
        print(" ".join(val))
    else:
        # Type of ``val`` remains as ``List[object]``.
        print("Not a list of strings!")

Дженерики

  • Generic
  • TypeVar
  • ParamSpec
  • ParamSpecArgs
  • ParamSpecKwargs
  • AnyStr эквивалент TypeVar('AnyStr', str, bytes)
  • Protocol(Generic) аннотирует проткол классов (поддерживает утиную типизацию в т.ч.)
class Proto(Protocol):
    def meth(self) -> int:
        ...

class C:
    def meth(self) -> int:
        return 0

def func(x: Proto) -> int:
    return x.meth()

func(C())  # Passes static type check
  • runtime_checkable рантайм проктол

Другие спец.директивы

В данном разделе не конструкции аннотации, а специальные типы, использующие аннотацию.

  • NamedTuple поддерживающий аннотацию вариант namedtuple
  • NewType
  • TypedDict тип, поддерживающий аннотацию для словаря

Коллекция дженериков

Основные типы: List, Dict, FrozenSet, Set

Типы, поддерживающие модуль collections: DefaultDict, OrderedDict, ChainMap, Counter, Deque (смотри [deque], [chainmap], [counter], [ordereddict], [defaultdict])

Другие типы (в основном депрекейтед)

ABC

Протоколы

Функции и декораторы

  • cast(typ, val) приводит переменную к типу, без изменеения значения
  • @overload Декоратор @overload позволяет описывать функции и методы, поддерживающие различные комбинации типов аргументов. За серией декорированных @overload определений должно следовать ровно одно недекорированное @overload определение
@overload
def process(response: None) -> None:
    ...
@overload
def process(response: int) -> tuple[int, str]:
    ...
@overload
def process(response: bytes) -> str:
    ...
def process(response):
    <actual implementation>
  • @final запрет переопределения
  • @no_type_check указание на то, что анотации не определяют тип
  • @no_type_check_decorator декоратор декоратора
  • @type_check_only определяет, что функция или класс не доступны в рантайм

Хелперы интроспекции

TYPE_CHECKING

True на првоерке и False в рантайм

if TYPE_CHECKING:
    import expensive_mod

def fun(arg: 'expensive_mod.SomeType') -> None:
    local_var: expensive_mod.AnotherType = other_fun()

Смотри еще: