从函数的关键字参数生成 TypedDict

问题描述 投票:0回答:2

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
检查通过。这实现了类型安全,但要求我在
consume
中复制
KWArgs
的关键字参数名称和类型。有没有办法在类型检查时从函数签名生成这个
TypedDict
,这样我就可以最大限度地减少维护中的重复?

python python-typing mypy
2个回答
5
投票

这将通过 PEP 692 在 Python 3.12 中提供:

from typing import TypedDict, Unpack, Required, NotRequired

class KWArgs(TypedDict):
    a: Required[int]
    b: NotRequired[str]

def consume(**kwargs: Unpack[KWArgs]) -> None:
    a = kwargs["a"]
    b = kwargs.get("b", ...)

consume()              # Not allowed.
consume(a=1)           # Allowed.
consume(a=1, b="abc")  # Allowed.

4
投票

据我所知,对此没有直接的解决方法[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__
,这在检查时是未知的,并定义一个装饰器来在运行时强制类型转换 -时间错过了类型检查的重点。

© www.soinside.com 2019 - 2024. All rights reserved.