← Назад к вопросам

Что такое Concatenate в модуле typing?

2.0 Middle🔥 71 комментариев
#Python Core#Soft Skills#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Concatenate в модуле typing

Concatenate — это специальный тип из модуля typing (Python 3.10+), который позволяет комбинировать параметры функции при использовании ParamSpec и callback типов. Это продвинутая фича для работы с функциональным программированием и декораторами.

Проблема, которую решает Concatenate

Когда ты пишешь декоратор, ты часто хочешь сохранить тип сигнатуры исходной функции, но добавить новые параметры:

from typing import Callable, TypeVar

# Исходная функция
def original_function(x: int, y: str) -> None:
    print(f"x={x}, y={y}")

# Декоратор, который добавляет параметр logger
def my_decorator(func: Callable[[int, str], None]) -> Callable[[int, str, str], None]:
    def wrapper(x: int, y: str, logger: str) -> None:
        print(f"[LOG: {logger}] Calling function")
        func(x, y)
    return wrapper

# Проблема: типы перестают совпадать,
# и type checker (mypy) ругается на несовместимость

Решение: Concatenate + ParamSpec

ParamSpec — захватывает сигнатуру функции (все её параметры), а Concatenate — добавляет новые параметры в начало:

from typing import Concatenate, ParamSpec, Callable, TypeVar

P = ParamSpec(P)  # Захватит все параметры
R = TypeVar(R)    # Тип возврата

def my_decorator(
    func: Callable[P, R]
) -> Callable[Concatenate[str, P], R]:
    """Декоратор добавляет параметр logger в начало"""
    def wrapper(logger: str, *args: P.args, **kwargs: P.kwargs) -> R:
        print(f"[{logger}] Calling function with args={args}, kwargs={kwargs}")
        return func(*args, **kwargs)
    return wrapper

# Использование
def greet(name: str, age: int) -> str:
    return f"Hello {name}, age {age}"

decorated_greet = my_decorator(greet)

# Теперь типы совпадают!
result = decorated_greet("DEBUG", "Alice", 30)  # logger="DEBUG", name="Alice", age=30
print(result)  # Hello Alice, age 30

Тип decorated_greet теперь корректно определён как:

Callable[[str, str, int], str]

Как это работает внутри

from typing import Concatenate, ParamSpec, Callable

P = ParamSpec(P)

# Исходная функция имеет сигнатуру (int, str) -> None
def func(x: int, y: str) -> None: ...

# Callable[P, None] = Callable[[int, str], None]
# Concatenate[str, P] расширяет P, добавляя str в начало
# Callable[Concatenate[str, P], None] = Callable[[str, int, str], None]

def decorator(
    f: Callable[P, None]
) -> Callable[Concatenate[str, P], None]:
    def wrapper(prefix: str, *args: P.args, **kwargs: P.kwargs) -> None:
        print(f"[{prefix}]")
        f(*args, **kwargs)
    return wrapper

Практический пример: декоратор с логированием

from typing import Concatenate, ParamSpec, Callable, TypeVar
import functools

P = ParamSpec(P)
R = TypeVar(R)

def with_logging(
    func: Callable[P, R]
) -> Callable[Concatenate[str, P], R]:
    """Добавляет параметр logger_name в начало функции"""
    @functools.wraps(func)
    def wrapper(logger_name: str, *args: P.args, **kwargs: P.kwargs) -> R:
        print(f"Logger: {logger_name}")
        print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
        result = func(*args, **kwargs)
        print(f"Result: {result}")
        return result
    return wrapper

# Применение декоратора
@with_logging
def calculate(x: int, y: int) -> int:
    return x + y

# Type checker знает, что первый параметр — строка
result = calculate("my_logger", 5, 10)  # OK
# result = calculate(5, 10)  # Error — missing logger_name argument

Несколько добавленных параметров

Можно добавить несколько параметров через вложенные Concatenate:

from typing import Concatenate, ParamSpec, Callable, TypeVar

P = ParamSpec(P)
R = TypeVar(R)

def with_metadata(
    func: Callable[P, R]
) -> Callable[Concatenate[str, int, P], R]:
    """Добавляет logger_name (str) и timeout (int)"""
    def wrapper(
        logger_name: str,
        timeout: int,
        *args: P.args,
        **kwargs: P.kwargs
    ) -> R:
        print(f"Logger: {logger_name}, Timeout: {timeout}s")
        return func(*args, **kwargs)
    return wrapper

@with_metadata
def fetch_data(url: str) -> str:
    return f"Data from {url}"

# logger_name, timeout, url
fetch_data("DEBUG", 30, "https://example.com")

Ограничения и когда использовать

Когда Concatenate полезен:

  • Декораторы, которые добавляют параметры
  • Wrapper функции с сохранением типов
  • Callback функции в libraries
# Полезный пример: middleware для FastAPI-подобного фреймворка
from typing import Concatenate, ParamSpec, Callable

P = ParamSpec(P)

def validate_auth(
    handler: Callable[P, dict]
) -> Callable[Concatenate[dict, P], dict]:  # Добавляем request dict
    def wrapper(request: dict, *args: P.args, **kwargs: P.kwargs) -> dict:
        if not request.get("token"):
            raise ValueError("Unauthorized")
        return handler(*args, **kwargs)
    return wrapper

Когда Concatenate НЕ нужен:

  • Простые функции без параметров
  • Если не используешь type checker (mypy, pyright)
  • Старые версии Python (< 3.10)

История и совместимость

# Python 3.10+ — встроено
from typing import Concatenate

# Python < 3.10 — из typing_extensions
from typing_extensions import Concatenate

# ParamSpec был добавлен в Python 3.10
# Для более старых версий используй typing_extensions

Резюме

Concatenate — это инструмент для сохранения типов параметров при создании декораторов и wrapper-функций. Он работает с ParamSpec и позволяет добавить новые параметры в начало сигнатуры функции, при этом сохраняя type safety.

Это продвинутая фича, но критична для:

  • Написания типобезопасных декораторов
  • Работы с callback функциями в libraries
  • Создания middleware и wrapper-ов

Если ты пишешь library code — знание Concatenate обязательно. Для обычного application code может быть избыточным.