【问题标题】:Generate TypedDict from function's keyword arguments从函数的关键字参数生成 TypedDict
【发布时间】:2020-09-15 22:22:51
【问题描述】:

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"

这是因为objectintstr 的超类型,因此可以推断。如果我声明:

from typing import TypedDict

class KWArgs(TypedDict):
    a: int
    b: str

然后将kwargs 注释为KWArgsmypy 校验通过。这实现了类型安全,但需要我在KWArgs 中复制consume 的关键字参数名称和类型。有没有办法在类型检查时从函数签名中生成这个TypedDict,这样我就可以最大限度地减少维护中的重复?

【问题讨论】:

  • 我在这里发表评论,看看在最新版本的 mypy 中是否有任何更新?

标签: python type-hinting mypy


【解决方案1】:

据我所知,[1] 没有直接的解决方法,但还有另一种优雅的方法可以实现这一点:

我们可以利用typings 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

它将识别ab 是参数,并批准它。

运行代码会输出:

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__,这在检查时是不知道的,并定义一个装饰器来转换运行时的类型错过了类型检查的重点。

【讨论】:

  • 很好的解决方案。在某些情况下,该函数是在第三方库中定义的,由于无法更改源,我无法创建函数的参数ConsumeContext。但这对我自己定义的函数很有用。我会把赏金留几天,因为有 1% 的机会可以在检查时通过函数签名完成,而不必获取 __annotations__,否则我会接受并给予赏金。
  • 我不喜欢这个解决方案,因为它使方法签名的可读性降低并且使用更加不透明。它犯了一个罪(使代码不那么可读)来防止另一个——可以说是不那么严重的——罪(重复代码);我不知道这是否值得权衡。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-10-25
  • 2016-12-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-16
相关资源
最近更新 更多