from functools import wraps
from typing import Any, Callable, Generic, ParamSpec, TypeVar
T = TypeVar("T")
P = ParamSpec("P")
class LazyOp(Generic[T]):
def __init__(self, func: Callable[..., T], args: tuple[Any, ...], kwargs: dict[str, Any]):
self.func = func
self.args = args
self.kwargs = kwargs
def __call__(self) -> T:
args = [a() if isinstance(a, LazyOp) else a for a in self.args]
kwargs = {k: v() if isinstance(v, LazyOp) else v for k, v in self.kwargs.items()}
return self.func(*args, **kwargs)
def lazy_decorator(func: Callable[P, T]) -> Callable[P, LazyOp[T]]:
@wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> LazyOp[T]:
return LazyOp(func, args, kwargs)
return wrapper
@lazy_decorator
def lazy_sum(a: int, b: int) -> int:
return a + b
lazy_result = lazy_sum(1, 2)
lazy_result = lazy_sum(
lazy_result, 4
) # Argument of type "LazyOp[int]" cannot be assigned to parameter "a" of type "int". "LazyOp[int]" is not assignable to "int"
result = lazy_result()
print(result)
Как включить автоматическую поддержку ленивых аргументов в функциях, декорированных @lazy_decorator, с помощью mypy?
Мне нужен способ включить функции @lazy_decorator, чтобы:
Сохранять исходные аннотации типов: функции по-прежнему должны отображаться в mypy так, как будто они принимают и возвращают свои исходные типы (например, int).
Прозрачное разрешение аргументов LazyOp: декорированные функции должны принимать как исходный тип (например, int), так и его ленивый вариант (LazyOp[int]), не вызывая ошибок mypy.
Избегайте ручная корректировка: не должно возникнуть необходимости переписывать аннотации типов для каждой декорированной функции или вводить дополнительные типы переноса в сигнатуру функции.
Бесшовная интеграция: решение не должно требовать внешних плагины, серьезную реструктуризацию или жертвование эффективностью выполнения.
По сути, мне нужен способ «научить» mypy тому, что декоратор расширяет типы аргументов, включая их ленивые эквиваленты, сохраняя при этом исходную аннотацию типа функции для ясности и удобства разработчика.
def __call__(self) -> T: args = [a() if isinstance(a, LazyOp) else a for a in self.args] kwargs = {k: v() if isinstance(v, LazyOp) else v for k, v in self.kwargs.items()} return self.func(*args, **kwargs)
@lazy_decorator def lazy_sum(a: int, b: int) -> int: return a + b
lazy_result = lazy_sum(1, 2)
lazy_result = lazy_sum( lazy_result, 4 ) # Argument of type "LazyOp[int]" cannot be assigned to parameter "a" of type "int". "LazyOp[int]" is not assignable to "int" result = lazy_result() print(result) [/code] Как включить автоматическую поддержку ленивых аргументов в функциях, декорированных @lazy_decorator, с помощью mypy? Мне нужен способ включить функции @lazy_decorator, чтобы: [list] [*]Сохранять исходные аннотации типов: функции по-прежнему должны отображаться в mypy так, как будто они принимают и возвращают свои исходные типы (например, int). [*]Прозрачное разрешение аргументов LazyOp: декорированные функции должны принимать как исходный тип (например, int), так и его ленивый вариант (LazyOp[int]), не вызывая ошибок mypy. [*]Избегайте ручная корректировка: не должно возникнуть необходимости переписывать аннотации типов для каждой декорированной функции или вводить дополнительные типы переноса в сигнатуру функции. [*]Бесшовная интеграция: решение не должно требовать внешних плагины, серьезную реструктуризацию или жертвование эффективностью выполнения. [/list] По сути, мне нужен способ «научить» mypy тому, что декоратор расширяет типы аргументов, включая их ленивые эквиваленты, сохраняя при этом исходную аннотацию типа функции для ясности и удобства разработчика.