Аннотация типов для *args и **kwargs в Python

Пройдите тест, узнайте какой профессии подходите

Я предпочитаю
0%
Работать самостоятельно и не зависеть от других
Работать в команде и рассчитывать на помощь коллег
Организовывать и контролировать процесс работы

Быстрый ответ

Чтобы указать типы элементов в *args, используйте Type для элементов одного типа и конструкцию Union[Type1, Type2], если элементы могут быть разных типов. Для **kwargs подходит Dict[KeyType, ValueType]. Если типы могут различаться, используйте Any.

Python
Скопировать код
def func(*args: int, **kwargs: str) -> None:
    pass  # args принимает целые числа, kwargs – строки

В Python 3.10 и более новых версиях для работы с типами используйте ParamSpec.

Python
Скопировать код
from typing import ParamSpec, Callable

P = ParamSpec('P')

def wrapper(func: Callable[P, int], *args: P.args, **kwargs: P.kwargs) -> int:
    return func(*args, **kwargs)  # вызывается функция с аргументами и ключевыми словами
Кинга Идем в IT: пошаговый план для смены профессии

Значение протоколов и ABC

Если ваши *args и **kwargs соответствуют определённому протоколу или интерфейсу ABC, применяйте Protocol и ABC в аннотациях типов для обозначения намерений и улучшения читаемости кода.

Python
Скопировать код
from typing import Protocol

class SupportsClose(Protocol):
    def close(self) -> None:
        ...

def close_all(*args: SupportsClose) -> None:
    for item in args:
        item.close()  # мы закрываем всё, что поддерживает этот метод

Могущественная перегрузка функций

Декоратор @overload позволяет объяснить различные комбинации параметров *args и соответствующих им возвращаемых типов, помогая статическому анализатору понять назначение вашей функции.

Python
Скопировать код
from typing import overload, Union

@overload
def process_values(*args: int) -> int: ...
@overload
def process_values(*args: str) -> str: ...

def process_values(*args: Union[int, str]) -> Union[int, str]:
    return sum(args) if all(isinstance(arg, int) for arg in args) else ','.join(args)

Строгая типизация *args и **kwargs с помощью TypedDict и Unpack

Используйте TypedDict для более точной типизации **kwargs, указывая ожидаемые ключи и их типы.

Python
Скопировать код
from typing import TypedDict

class Options(TypedDict):
    user: str
    retries: int

def connect(**kwargs: Options) -> None:
    ...  # осуществляем соединение с сервисом

В Mypy 0.981+ вы можете также использовать Unpack для распаковки типов:

Python
Скопировать код
from typing_extensions import Required, NotRequired, Unpack

class MyParams(TypedDict):
    a: Required[int]
    b: NotRequired[str]

def my_func(*args: Unpack[MyParams]) -> None:
    ...  # функция принимает распакованные аргументы

Визуализация

Проведём аналогию: упаковка вещей на отпуск работает по принципу *args и **kwargs. У нас есть:

  • Чемодан (🧳) для *args – складываем разные вещи без организации
  • Рюкзак (🎒) для **kwargs – отдельные карманы для конкретных предметов

Типизируем содержимое:

Python
Скопировать код
def pack_vacation(*clothes: str, **items: str) -> None:
    pass  # как следует упаковывать вещи?

Чемодан: [👖, 👕, 👗, 🩳] Рюкзак: {passport: "📄", suncream: "🧴", sunglasses: "🕶️"}

Теперь каждый предмет оснащён ярлычком с типом, что обеспечивает беззаботное путешествие! 🏖️✨

Конвенции и сложности с *args и **kwargs

Работа с *args

*args может принимать произвольное количество аргументов. Невозможно ограничить число элементов, если они типа int:

Python
Скопировать код
def sum_integers(*args: int) -> int:
    return sum(args)  # *args "принимает" неограниченное количество чисел

Смешивание типов

Если *args принимает значения различных типов, используйте object и реализуйте проверку типов во время выполнения:

Python
Скопировать код
def combine(*args: object) -> str:
    return " ".join(str(arg) for arg in args if isinstance(arg, (str, int, float)))  
    # args могут быть разных типов

Сохранение сигнатуры

В Python 3.10 вы можете контролировать типы *args и **kwargs, используя ParamSpec:

Python
Скопировать код
from typing import ParamSpec
P = ParamSpec('P')

def repeat_call(func: Callable[P, None], times: int, *args: P.args, **kwargs: P.kwargs) -> None:
    for _ in range(times):
        func(*args, **kwargs)  # вызываем функцию несколько раз

Учет Python 2

В Python 2 аннотации типов не поддерживаются, но можно использовать комментарии для указания типов:

Python
Скопировать код
def historical_func(*args, **kwargs):
    # type: (*int, **str) -> None
    pass  # заглянем в прошлое языка Python

Полезные материалы

  1. PEP 484 – Type Hints — основа аннотаций типов в Python.
  2. typing — Support for type hints — Python 3.12.2 documentation — официальное руководство по типизации в Python.
  3. Python Type Checking (Guide) – Real Python — практическое применение проверки типов.
  4. PEP 3102 – Keyword-Only Arguments — об аргументах, которые передаются только по ключу.
  5. PEP 3107 – Function Annotations — глубже в мир аннотаций функций Python.
  6. mypy – Examples — примеры использования проверки типов с помощью mypy.
  7. Python args and kwargs: Demystified – Real Python — подробно о *args и **kwargs.