It’s more than a little common to take (*args, **kwargs) and then save them or pass them elsewhere as (args, kwargs), but currently there’s no way (that I can find) to type such a thing.
Please consider allowing a type that’s parameterized on a ParamSpec P to use P.args and P.kwargs as annotations outside of the currently required *P.args, **P.kwargs constraint.
Examples:
A)
This class is currently allowed, and the type of Call.args here is P.args but there does not exist a valid explicit annotation for it:
B)
This signature is currently impossible to annotate. It will need some major invasive surgery to become typed, but it’s not even clear what that change would be.
class _lru_cache_wrapper(Generic[P, T]):
def make_key(self, args: P.args, kwds: P.kwargs): ...
C)
I’d like to propose allowing these (or some equivalent) definitions:
(and in fact, if you rely on the type of self.args or self.kwargs being narrower than this, Paramspec use isn’t appropriate to begin with)
If this is for typing functools.lru_cache as appears to be indicated by example B, make_key doesn’t need the precise types, it operates on arbitrary args and kwargs, so this is okay here too.
It’s unfortunate that people view Any as a magic off switch for typing and try to avoid it as a result, sometimes it’s the correct type, including when you accept anything and don’t rely on the specifics of the type, such as exactly the case with functools cache handling of make_key
(object isn’t correct here because of the variance of dict)
I’d also be curious if it’s feasible and consistent to allow it.
No, that wouldn’t work. The problem is that callable signatures can include parameters that receive values from either positional or keyword arguments. The mix of positional vs keyword args can vary from caller to caller.
If a ParamSpec is specialized to the signature of func in this example, there’s no way to know statically whether there are zero, one or two positional args.
This is why P.args and P.kwargs always need to be used together. As a pair, they describe the entire input signature. Each of these by itself has no valid meaning. For that reason, tuple[P.args, P.kwargs] doesn’t make sense.