It seems that for historical reasons – PEP 612 – Parameter Specification Variables became a part of the language one major version before PEP 646 – Variadic Generics – we have been annotating ParamSpec like *args: P.args rather than *args: *P.args, which would logically make more sense, seeing that P.args is a kind of variadic tuple type.
What’s stopping us, other than convention, to simply interpret the star argument in the last call as tuple[int, *P.args], and mapping this successfully onto Concatenate[int, P]?
Is there a use case? The currently working code looks nicer in your example.
That’s not really true, right? You always have to combine P.args with P.kwargs, because for most Python functions, arguments can be passed in positionally or with a keyword. If you have a function
def f (a: int, b: str = "") -> None: ...
then what does tuple[int, *P.args] mean? It seems to depend on how the function was called.
Yes, P.args depends on P.kwargs. Types depending on other types is a very common thing, e.g. def f[T](x: T) -> T: .... Why would that mean that it cannot be variadic tuple type?
I believe that ParamSpec was only ever designed to provide type safety of a signature, treating args and kwargs as inseparable parts of a conceptually-whole signature object.
In an alternate universe, if syntax design were a little more adventurous, we may have seen ParamSpec and type parameters been introduced like this instead:
This makes no room for the user to think that it’s even possible to wrestle with type checkers to somehow achieve type safety by doing something individually with args or kwargs within the function body (test).
Slightly related, PEP 677 with an easier-to-parse and more expressive syntax was a discussion to improve how to annotate callables, not sure what came of it though. You might want to look at that and see how your idea would work with the proposal there (sorry if you’ve already seen it).