Hello,
I’m struggling to type a function decorator which:
- takes arguments itself, and
- injects different args into the decorated function calls, and
- decorates functions with different args/kwargs.
An minimal example would be the following:
import collections.abc
import functools
import typing
# P = typing.ParamSpec("P")
# R = typing.TypeVar("R")
# https://docs.python.org/3/reference/compound_stmts.html#generic-type-aliases
# type F[**P, R] = (
# collections.abc.Callable[typing.Concatenate[int, float, P], R]
# | collections.abc.Callable[typing.Concatenate[float, P], R]
# )
type F[**P, R] = collections.abc.Callable[P, R]
def decorator[**P, R](with_x: bool = False) -> collections.abc.Callable[[F[..., R]], F[..., R]]:
def outer(wrapped: F[..., R]) -> F[..., R]:
@functools.wraps(wrapped)
def inner(*args: P.args, **kwargs: P.kwargs) -> R:
# This inner() function can actually be quite complex!
f = 3.14 # `f` is passed to *all* decorated functions.
if with_x:
x = 1 # `x` is passed only if `with_x` is truthy.
return wrapped(x, f, *args, *kwargs)
return wrapped(f, *args, kwargs)
return inner
return outer
@decorator(with_x=True)
def fun1(x: int, f: float, s: str) -> str:
return s.lower()
@decorator(with_x=False)
def fun2(f: float, s: str, d: dict[str, typing.Any]) -> str:
return s.lower() + "".join(d.keys())
This code fails because error: ParamSpec "P" is unbound
and it’s missing actual types for the call parameters (the ...
ellipsis).
As show, depending on the decorator argument with_x
the decorator function inserts either x
and f
args, or just f
into the function call to the decorated function. The rest of the args/kwargs of the decorated function can be anything.
Thank you heaps!
PS: Somewhat related seems Type Hinting with Decorator Removing First Argument