问题
foo.py
:
kwargs = {"a": 1, "b": "c"}
def consume(*, a: int, b: str) -> None:
pass
consume(**kwargs)
mypy foo.py
:
error: Argument 1 to "consume" has incompatible type "**Dict[str, object]"; expected "int"
error: Argument 1 to "consume" has incompatible type "**Dict[str, object]"; expected "str"
This is because object
is a supertype of int
and str
, and is therefore inferred. If I declare:
from typing import TypedDict
class KWArgs(TypedDict):
a: int
b: str
and then annotate kwargs
as KWArgs
, the mypy
check passes. This achieves type safety, but requires me to duplicate the keyword argument names and types for consume
in KWArgs
. Is there a way to generate this TypedDict
from the function signature at type checking time, such that I can minimize the duplication in maintenance?
回答1:
To the best of my knowledge, there is no direct workaround on this [1], but there is another elegant way to achieve exactly that:
We can utilize the typing
s NamedTuple
to create an object that holds the parameter:
ConsumeContext = NamedTuple('ConsumeContext', [('a', int), ('b', str)])
Now we define the consume
method to accept it as a parameter:
def consume(*, consume_context : ConsumeContext) -> None:
print(f'a : {consume_context.a} , b : {consume_context.b}')
The whole code would be:
from typing import NamedTuple
ConsumeContext = NamedTuple('ConsumeContext', [('a', int), ('b', str)])
def consume(*, consume_context : ConsumeContext) -> None:
print(f'a : {consume_context.a} , b : {consume_context.b}')
ctx = ConsumeContext(a=1, b='sabich')
consume(consume_context=ctx)
And running mypy would yield:
Success: no issues found in 1 source file
It will recognize that a
and b
are parameters, and approve that.
And running the code would output:
a : 1 , b : sabich
However, if we change b
to be not a string, mypy will complain:
foo.py:9: error: Argument "b" to "ConsumeContext" has incompatible type "int"; expected "str"
Found 1 error in 1 file (checked 1 source file)
By this, we achieve type checking for a method by defining once it's parameters and types.
[1] Because if either defining TypedDict
or function signature, based on the other, would require to know the other's __annotations__
, which is not known on check-time, and defining a decorator to cast types on run-time misses the point of type checking.
来源:https://stackoverflow.com/questions/63910610/generate-typeddict-from-functions-keyword-arguments