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

时间:2018-07-04 03:46:01

标签: python python-3.x types mypy

给出Accessing dict keys like an 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检查的类和函数添加类型提示的正确方法是什么:

  • 字典键将始终为str
  • dict值将必须是动态的,并由Any表示,因为它们不一样,我不希望分别输入,例如一些strList[dict[str, List]],{{1} },Dict[str, str]

1 个答案:

答案 0 :(得分:1)

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

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 AttrDict type别名Any的em>:完全等同于Any。但是,在运行时,我们总是会陷入“ else”分支并像以前一样定义类。

此方法的主要缺点是,使用静态类型确实没有任何价值。我们现在可以强制要求键必须是字符串,因此我们可以为dict_to_attrdict添加一点安全性。

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

这使mypy可以了解存在的字段,字段的类型等。需要做一些前期工作,但好处是mypy可以为您提供代码正确性的有力保证。

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

如果您想使用数据类,则可能要等到本周二发布mypy 0.620时-我不记得该功能是否将其添加到mypy 0.600或mypy 0.610中。