Что такое Concatenate в модуле typing?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
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 может быть избыточным.