我越来越多地使用类型提示和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()
答案 0 :(得分:3)
Mypy进行了一些非常高级的类型推断。通常,您不需要注释变量。 mypy文档[1]关于推理的内容:
Mypy将初始分配视为变量的定义。如果您未明确指定变量的类型,则mypy将根据值表达式的静态类型来推断类型
通常的经验法则是“注释类型在初始分配时不可推断的变量”。
以下是一些示例:
空容器。如果我将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
答案 1 :(得分:3)
设计Mypy(通常是PEP 484)的目的是,在最理想的情况下,您只需要在代码的“边界”或“接口”中添加类型注释即可。
例如,您基本上必须在以下位置添加注释/类型元数据:
class MyClass(Dict[int, str]): ...
,而不是class MyClass(dict): ...
。这些都是代码“边界”的示例。在参数/返回类型上的类型提示使函数的调用者可以确保它们正确地调用它,在字段上的类型提示可以使调用者知道它们正确地使用了对象,等等。
然后,Mypy(以及其他符合PEP 484的工具)将使用该信息,并尝试推断其他所有类型。此行为旨在大致模拟人类如何阅读代码:例如,一旦知道传入的类型,通常很容易理解其余代码的作用。
毕竟,Python是一种从一开始就被设计为易于阅读的语言!我们不需要在所有地方分散类型提示来增强对代码功能的理解。
当然,mypy(和其他符合PEP 484的工具)并不完美,有时它们可能无法正确推断某些局部变量的类型。在这种情况下,您可能需要添加类型提示来帮助mypy。 Ethan's answer很好地概述了一些需要注意的常见情况。 (有趣的是,这些情况也往往是人类读者可能难以理解您的代码的示例!)
因此,将所有内容放在一起,通常的建议是:
因此,回到您的示例,您将不在两种情况下都添加类型提示。普通读者和mypy都可以告诉您_volume
字段必须为浮点数:因为参数为浮点数并将浮点数乘以int会始终产生另一个浮点数,因此很明显必须是这种情况。>
类似地,您将不添加注释到您的my_volume
变量中。由于return_volume()
具有类型提示,因此很容易查看返回的类型并了解my_volume
是float类型。 (而且,如果您犯了一个错误,并且不小心认为是浮动以外的其他东西,那么mypy会帮您抓住它。)
答案 2 :(得分:1)
对于我自己,它开始在可能的任何地方编写类型提示。它一点也不慢,并且如果您返回到该功能中的旧代码,它会变得更加容易。因此,除了python文件的大小之外,现在在尽可能多地使用它们方面存在负面影响。