I am currently trying to learn how to best include types in my Python code, but I'm a little perplexed on how to properly type **kwargs
in my function signatures.
A minimal example of what I currently have:
def foo(name: str, **kwargs: Dict[str, Any]) -> int:
modified_name: str = f'This is an example for {name}!'
return some_other_func(modified_name, **kwargs)
def bar(id_number: int, **kwargs: Dict[str, Any]) -> int:
name: str = some_func(id_number)
return foo(name, number=id_number, **kwargs)
What I expect to happen: Since foo
does not specify the number
keyword argument, I expect it to get collected into foo
's kwargs
, which is essentially a Dict[str, Any]
. Since "int" is an "Any" type I should have no complaints about typing.
What really happens: At runtime number
is collected into foo
's kwarg
s and some_other_func
can safely pull out the number
keyword argument. However, mypy (version 1.1.1) complains with the following
error: Argument "number" to "foo" has incompatible type "int"; expected "Dict[str, Any]"
A similar complaint from pyright (version 1.1.292):
Argument of type "int" cannot be assigned to parameter "number" of type "Dict[str, Any]" in function "foo" "int" is incompatible with "Dict[str, Any]"
Is this expected behavior? Should I expect type checkers to complain if I am passing keyword arguments the direct callee doesn't have in the function signature?
Continuing this I thought okay, I will just add number
as a key in kwargs
directly (whether this is good practice I'm not sure, but this is besides the point), so this way I will certainly be passing a Dict[str, Any]
. This is essentially what I tried:
def bar(id_number: int, **kwargs: Dict[str, Any]) -> int:
name: str = some_func(id_number)
kwargs[number] = id_number
return foo(name, **kwargs)
But now I get the error (from mypy):
error: Incompatible types in assignment (expression has type "int", target has type "Dict[str, Any]"
Similarly from pyright:
Argument of type "int" cannot be assigned to parameter "__value" of type "Dict[str, Any]" in function "__setitem__" "int" is incompatible with "Dict[str, Any]"
But this is even more confusing, why do they seem to imply I'm assigning to kwargs
when really I'm assigning to a key in kwargs
?
Does it then mean I'm supposed to be typing the values inside of kwargs
then?
If instead I type kwargs
as "Union[int, Dict[str, Any]]" I get no issue. So this seems to confirm the question. But why the different behavior? Is it simply convenience that we should know kwargs
is a mapping, so avoid the extra typing wrap? But I don't see this stated anywhere and I'm not 100% convinced I am correct on how this works.
Finally, I've also tried using Mapping[str, Any]
intead of Dict[str, Any]
, but it has same issue.
tl;dr: kwargs
is a dictionary containing keywords not specified by the function signature and it can be accessed and modified like a Dict[str, Any]
. Why can't I type it like one? How should I really type it?
When you using **kwargs
, the key is always going to be a string. For type-checking the value of that key, you only need to type check one possible value for the key.
For more detail, check out this question and it's answer.