类型提示:何时注释

Type hints: when to annotate

我越来越多地使用类型提示和 mypy。然而,我对何时应该显式注释声明以及何时可以由 mypy 自动确定类型有一些疑问。

例如:

def assign_volume(self, volume: float) -> None:
    self._volume = volume * 1000

我应该写吗

self._volume: float = volume *1000

在这种情况下?

现在如果我有以下功能:

def return_volume(self) -> float:
        return self._volume

在我的代码中某处:

my_volume = return_volume()

我应该写:

my_volume: float = return_volume()

对于我自己,它开始在任何可能的地方编写类型提示。它一点也不慢,如果您返回到该功能中的旧代码,它会变得更容易。因此,除了 python 文件的大小之外,现在尽可能多地使用它们存在负面影响。

Mypy 做了一些非常高级的类型推断。通常,您不需要注释变量。 mypy 文档 [1] 是这样描述推理的:

Mypy considers the initial assignment as the definition of a variable. If you do not explicitly specify the type of the variable, mypy infers the type based on the static type of the value expression

一般的经验法则是 "annotate variables whose types are not inferrable at their initial assignment"。

这里有一些例子:

  • 空容器。如果我将 a 定义为 a = [],mypy 将不知道列表 a.

  • 中哪些类型有效
  • Optional 类型。通常,如果我定义一个 Optional 类型,我会将变量分配给 None。例如,如果我做 a = None,mypy 将推断 a 的类型为 NoneType,如果你想稍后将 a 分配给 5,你需要注释它:a: Optional[int] = None.

  • 复杂的嵌套容器。例如,如果您有一个包含列表和字符串值的字典,mypy 可能会推断出 Dict[str, Any]。您可能需要对其进行注释以使其更准确。

当然还有很多情况

在您的示例中,mypy 可以推断表达式的类型。

[1] https://mypy.readthedocs.io/en/latest/type_inference_and_annotations.html

Mypy(和一般的 PEP 484)的设计使得在最理想的情况下,您只需将类型注释添加到代码的 "boundaries" 或 "interfaces"。

例如,您基本上必须在以下位置添加 annotations/type 元数据:

  1. 参数和return类型的函数和方法。
  2. 任何对象字段(假设仅通过查看构造函数无法推断出字段类型)
  3. 当你继承一个class。例如,如果你特别想将一个 dict of ints 子class subclass 到 strs,你应该做 class MyClass(Dict[int, str]): ...,而不是 class MyClass(dict): ....

这些都是您的代码 "boundaries" 的例子。在 parameter/return 类型上键入提示让函数的调用者确保他们正确调用它,在字段上键入提示让调用者知道他们正在正确使用对象,等等...

Mypy(和其他 PEP 484 兼容工具)然后将使用该信息并尝试推断 其他所有内容的类型。这种行为旨在大致模仿人类阅读代码的方式:例如,一旦您知道传入的是什么类型,通常就很容易理解其余代码的作用。

毕竟,Python 是一种从一开始就设计为可读性强的语言!我们不需要到处散布类型提示来增强我们对代码功能的理解。

当然,mypy(和其他 PEP 484 兼容工具)并不完美,有时它们可​​能无法正确推断出某些局部变量的类型。在这种情况下,您可能需要添加类型提示来帮助 mypy。 很好地概述了一些需要注意的常见情况。 (有趣的是,这些案例也往往是 人类 reader 可能难以理解您的代码的示例!)

因此,要将所有内容放在一起,一般建议是:

  1. 向代码的所有 "boundaries" 添加类型提示,例如函数参数和 return 类型。
  2. 默认为注释变量。如果 mypy 无法推断某个变量应该是什么类型,请添加注释来帮助它。
  3. 如果您发现自己需要注释大量变量以使 mypy 满意,请考虑重构您的代码。如果 mypy 很容易混淆,那么人类 reader 也可能很容易混淆。

因此,回到您的示例,您将在任何一种情况下添加类型提示。人类 reader 和 mypy 都可以判断出您的 _volume 字段必须是一个浮点数:很明显必须是这种情况,因为参数是一个浮点数并且将一个浮点数乘以一个整数总是会产生另一个浮动。

同样,您 不会 my_volume 变量添加注释。因为 return_volume() 有类型提示,所以很容易看出它是什么类型 returning 并理解 my_volume 是 float 类型。 (如果你犯了一个错误,不小心认为它不是浮点数,那么 mypy 会为你捕捉到它。)