在 python 3.6 中通过属性符号表示字典键时的类型提示

Type hints when representing dict keys by attribute notation in python 3.6

以第一个答案为例Accessing dict keys like an attribute?

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

和一个 returns:

的函数
def dict_to_attrdict(somedict):
    return AttrDict(**somedict)

分配为:

data = dict_to_attrdict(mydict)

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

您可以通过以下方式对 class 和函数定义本身进行类型检查:

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' 分支中的任何内容:我们现在已经告诉 mypy AttrDicttype alias for Any: 完全等同于 Any。然而,在运行时,我们总是落入 'else' 分支并像以前一样定义 class

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

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

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

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

如果你想使用 dataclasses,你可能需要等到 mypy 0.620 在即将到来的周二发布——我不记得这个功能是在 mypy 0.600 还是 mypy 0.610 中出现的.