Consider the following attempt at add adding type hints to the functions parent
and child
:
def parent(*, a: Type1, b: Type2):
...
def child(*, c: Type3, d: Type4, **kwargs):
parent(**kwargs)
...
MyPy complains that kwargs
has the type Dict[str, Any]
but that the arguments a
and b
require Type1
and Type2
respectively.
I understand that the solution to this error is to rewrite child
in the following way:
def child(*, a: Type1, b: Type2, c: Type3, d: Type4, **kwargs):
parent(a=a, b=b)
...
However, what if the argument list of parent
is much longer, or there is function grandchild
which has its own argument list and must call child
. Are you required to enumerate the arguments and their types from all the downstream functions? Or is there a graceful way to handle the "per-key" typing of **kwargs
?
With the advent of TypedDict
in PEP-692 this has technically been solved:
class ParentKwargs(TypedDict):
a: Type1
b: Type2
def parent(**kwargs: Unpack[ParentKwargs]):
...
class ChildKwargs(ParentKwargs):
c: Type3
d: Type4
def child(**kwargs: Unpack[ChildKwargs]):
...
Though perhaps not in an entirely ideal fashion - you now have to destructure the kwargs to use them and dealing with default values isn't pretty:
class ParentKwargs(TypedDict):
a: Type1
b: Type2
def parent(**kwargs: Unpack[ParentKwargs]):
a = kwargs["a"]
b = kwargs["b"]
class OptionalChildKwargs(TypedDict, total=False):
c: Type3
class ChildKwargs(OptionalChildKwargs, ParentKwargs):
d: Type4
def child(**kwargs: Unpack[ChildKwargs]):
c = kwargs.get(default_value)
d = kwargs["d"]
At this point there's so much visual noise that it would probably be better to just enumerate all the kwargs as you would have needed to to absent TypedDict
.