问题
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 typings 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