Sometimes the need arises for a ParamSpec that only allows keyword arguments. Recently, I saw this PR on typeshed: Make `copy.replace` more strongly typed by decorator-factory · Pull Request #14819 · python/typeshed · GitHub where the problem is that we want copy.replace() to inherit the signature from .__replace__() but we have to make sure that copy.replace() only accepts keyword arguments (except for the first one, which is the object and is positionally-only). It therefore seems natural to define the protocol for __replace__() like this:
class _SupportsReplace[**P, T](Protocol):
def __replace__(self, /, **kwargs: P.kwargs) -> T:
...
where we only used the P.kwargs part of the ParamSpec and not the P.args part, in order to signal that any implementation of this protocol should enforce keyword arguments, like this:
class A:
def __init__(self, x: int):
self.x = x
def __replace__(self, /, **kwargs) -> Self:
return A(x=kwargs.get("x", self.x))
Then, copy.replace() can be typed like this:
def replace[**P, T](obj: _SupportsReplace[P, T], *args: P.args, **kwargs: P.kwargs) -> T:
return obj.__replace__(*args, **kwargs)
Well, or like this:
def replace[**P, T](obj: _SupportsReplace[P, T], **kwargs: P.kwargs) -> T:
return obj.__replace__(**kwargs)
but it shouldn’t matter which one.
So, are there any soundness issue with allowing only using P.kwargs?