从函数的关键字参数生成 TypedDict
Generate TypedDict from function's keyword arguments
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"
这是因为 object
是 int
和 str
的超类型,因此被推断出来。如果我声明:
from typing import TypedDict
class KWArgs(TypedDict):
a: int
b: str
然后将kwargs
注释为KWArgs
,mypy
检查通过。这实现了类型安全,但需要我在 KWArgs
中复制 consume
的关键字参数名称和类型。有没有办法在类型检查时从函数签名生成这个 TypedDict
,这样我就可以最大限度地减少维护中的重复?
据我所知,[1] 没有直接的解决方法,但有另一种优雅的方法可以做到这一点:
我们可以利用 typing
s NamedTuple
创建一个包含参数的对象:
ConsumeContext = NamedTuple('ConsumeContext', [('a', int), ('b', str)])
现在我们定义consume
方法来接受它作为参数:
def consume(*, consume_context : ConsumeContext) -> None:
print(f'a : {consume_context.a} , b : {consume_context.b}')
整个代码为:
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)
而 运行 mypy 会产生:
Success: no issues found in 1 source file
会识别a
和b
为参数,并认可。
而 运行 代码将输出:
a : 1 , b : sabich
但是,如果我们将 b
更改为不是字符串,mypy 会报错:
foo.py:9: error: Argument "b" to "ConsumeContext" has incompatible type "int"; expected "str"
Found 1 error in 1 file (checked 1 source file)
这样,我们通过定义一次方法的参数和类型来实现对方法的类型检查。
[1] 因为如果根据另一个定义 TypedDict
或函数签名,则需要知道另一个 __annotations__
,而这在 check-time,并定义一个装饰器以在 run-time 上进行类型转换错过了类型检查的要点。
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"
这是因为 object
是 int
和 str
的超类型,因此被推断出来。如果我声明:
from typing import TypedDict
class KWArgs(TypedDict):
a: int
b: str
然后将kwargs
注释为KWArgs
,mypy
检查通过。这实现了类型安全,但需要我在 KWArgs
中复制 consume
的关键字参数名称和类型。有没有办法在类型检查时从函数签名生成这个 TypedDict
,这样我就可以最大限度地减少维护中的重复?
据我所知,[1] 没有直接的解决方法,但有另一种优雅的方法可以做到这一点:
我们可以利用 typing
s NamedTuple
创建一个包含参数的对象:
ConsumeContext = NamedTuple('ConsumeContext', [('a', int), ('b', str)])
现在我们定义consume
方法来接受它作为参数:
def consume(*, consume_context : ConsumeContext) -> None:
print(f'a : {consume_context.a} , b : {consume_context.b}')
整个代码为:
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)
而 运行 mypy 会产生:
Success: no issues found in 1 source file
会识别a
和b
为参数,并认可。
而 运行 代码将输出:
a : 1 , b : sabich
但是,如果我们将 b
更改为不是字符串,mypy 会报错:
foo.py:9: error: Argument "b" to "ConsumeContext" has incompatible type "int"; expected "str"
Found 1 error in 1 file (checked 1 source file)
这样,我们通过定义一次方法的参数和类型来实现对方法的类型检查。
[1] 因为如果根据另一个定义 TypedDict
或函数签名,则需要知道另一个 __annotations__
,而这在 check-time,并定义一个装饰器以在 run-time 上进行类型转换错过了类型检查的要点。