首页 > 解决方案 > 在 python 3.6 中通过属性表示法表示 dict 键时的类型提示

问题描述

给出Accessing dict keys like a attribute 中第一个答案的示例?

class AttrDict(dict):
    def __init__(self, *args, **kwargs):
        super(AttrDict, self).__init__(*args, **kwargs)
        self.__dict__ = self

和一个返回的函数:

def dict_to_attrdict(somedict):
    return AttrDict(**somedict)

分配为:

data = dict_to_attrdict(mydict)

在给定以下约束的情况下,为将通过 mypy 检查的类和函数添加类型提示的正确方法是什么:

标签: pythonpython-3.xtypesmypy

解决方案


您可以通过执行以下操作使类和函数定义本身进行类型检查:

from typing import Dict, Any

class AttrDict(Dict[str, Any]):
    def __init__(self, *args: Any, **kwargs: Any) -> None:
        super(AttrDict, self).__init__(*args, **kwargs)
        self.__dict__ = self

def dict_to_attrdict(some: Dict[str, Any]) -> AttrDict:
    return AttrDict(**some)

继承自Dict[X, Y]与仅dict在运行时继承自没有什么不同,但它为 mypy 提供了所需的额外元数据。

但是,您实际上无法以AttrDict类型安全的方式使用 的实例:mypy 将始终将诸如错误之类的事情标记my_attrdict.foo为错误。

这是因为不可能静态地确定AttrDict在所有情况下会出现哪些字段——mypy 不知道里面到底有什么AttrDict。而且由于 mypy 无法判断做类似my_attrdict.foo的事情实际上是否安全,它倾向于保守的一面,并决定认为这是不安全的。

您有两种不同的选择来解决这个问题。首先,如果您真的想尽可能保持AttrDict动态,您可以告诉 mypy 假设该类型是任意动态类型,如下所示:

from typing import Dict, Any, TYPE_CHECKING

if TYPE_CHECKING:
    AttrDict = Any
else:
    class AttrDict(dict):
        def __init__(self, *args, **kwargs) -> None:
            super(AttrDict, self).__init__(*args, **kwargs)
            self.__dict__ = self

def dict_to_attrdict(some: Dict[str, Any]) -> AttrDict:
    return AttrDict(**some)

TYPE_CHECKING是一个在运行时始终为 False 的值,但 mypy 将其视为始终为 True。最终效果是 mypy 将只考虑 if/else 的“if”分支,并忽略“else”分支中的任何内容:我们现在已经教 mypyAttrDict是 : 的类型别名Any完全等同于Any. 然而,在运行时,我们总是落入“else”分支并像以前一样定义类。

这种方法的主要缺点是我们实际上没有从使用静态类型中获得任何价值。我们可以增加一点安全性,dict_to_attrdict因为我们现在可以强制密钥必须是字符串,但仅此而已。

第二种选择是利用 mypy 的优势并重写您的代码以实际使用类。因此,我们将摆脱AttrDict并实际使用设置字段的类。

这可以让 mypy 了解存在哪些字段,它们的类型是什么等。这需要更多的前期工作,但好处是 mypy 能够为您的代码的正确性提供更强的保证。

如果您发现实际上定义一堆带字段的类很乏味,请尝试使用新的“dataclasses”模块(如果您使用的是 Python 3.7)或第 3 方“attrs”模块。我相信 mypy 最近增加了对两者的支持。

不过,如果您想使用数据类,您可能必须等到 mypy 0.620 在即将到来的星期二发布——我不记得该功能是在 mypy 0.600 还是 mypy 0.610 中使用的。


推荐阅读