我越来越多地使用类型提示和 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()
Mypy(以及一般的 PEP 484)的设计使得在最理想的情况下,您只需要向代码的“边界”或“接口”添加类型注释。
例如,您基本上必须在以下位置添加注释/类型元数据:
class MyClass(Dict[int, str]): ...
,而不是 class MyClass(dict): ...
。这些都是代码“边界”的示例。参数/返回类型上的类型提示让函数的调用者确保他们正确调用它,字段上的类型提示让调用者知道他们正在正确使用对象,等等...
然后,Mypy(以及其他 PEP 484 兼容工具)将使用该信息并尝试推断其他所有内容的类型。这种行为旨在大致模仿人类阅读代码的方式:例如,一旦您知道传入的类型,通常就很容易理解代码的其余部分的作用。
毕竟,Python 是一门从一开始就被设计为可读性的语言!我们不需要到处散布类型提示来增强我们对代码功能的理解。
当然,mypy(和其他符合 PEP 484 的工具)并不完美,有时它们可能无法正确推断某些局部变量的类型。在这种情况下,您可能需要添加类型提示来帮助 mypy 进行操作。 Ethan 的回答 很好地概述了一些需要注意的常见情况。 (有趣的是,这些案例也往往是“人类”读者可能难以理解您的代码的示例!) 因此,将所有内容放在一起,一般建议是:
向代码的所有“边界”添加类型提示,例如函数参数和返回类型。
添加类型提示。人类读者和 mypy 都可以告诉你的 _volume
字段必须是浮点数:很明显,情况一定是这样,因为参数是浮点数,并且将浮点数乘以 int 总是会产生另一个浮点数。
同样,您也不会
向您的my_volume
变量添加注释。由于
return_volume()
具有类型提示,因此很容易看到它返回的类型并了解 my_volume
是 float 类型。 (如果你犯了一个错误,无意中认为它不是浮点数,那么 mypy 会为你捕获它。)Mypy 将初始赋值视为变量的定义。如果没有显式指定变量的类型,mypy 会根据值表达式的静态类型推断类型
一般的经验法则是“注释其类型在初始赋值时不可推断的变量”。
以下是一些示例:
a
定义为
a = []
,mypy 将不知道列表中哪些类型是有效的 a
。
Optional
类型。通常,如果我定义
Optional
类型,我会将变量分配给 None
。例如,如果我这样做a = None
,mypy会推断a
具有类型NoneType
,如果稍后你想将a
分配给5
,你需要注释它:a: Optional[int] = None
。
Dict[str, Any]
。您可能需要对其进行注释才能更准确。
在您的示例中,mypy 可以推断表达式的类型。
[1]
https://mypy.readthedocs.io/en/latest/type_inference_and_annotations.html