JPF*_*oia 2 python type-hinting mypy
让我们考虑以下两种语法变体:
class Foo:
x: int
def __init__(self, an_int: int):
self.x = an_int
Run Code Online (Sandbox Code Playgroud)
和
class Foo:
def __init__(self, an_int: int):
self.x = an_int
Run Code Online (Sandbox Code Playgroud)
显然,以下代码在两种情况下都会引发 mypy 错误(这是预期的):
obj = Foo(3)
obj.x.title() # this is a str operation
Run Code Online (Sandbox Code Playgroud)
但我真的想强制执行契约:我想明确说明 x 是每个Foo对象的实例变量。那么应该首选哪种语法,为什么?
这最终是个人喜好的问题。要在另一个答案中使用示例,请同时执行以下操作:
class Foo:
x: Union[int, str]
def __init__(self, an_int: int) -> None:
self.x = an_int
Run Code Online (Sandbox Code Playgroud)
...并做:
class Foo:
def __init__(self, an_int: int) -> None:
self.x: Union[int, str] = an_int
Run Code Online (Sandbox Code Playgroud)
...将被类型检查器以完全相同的方式处理。
执行前者的主要优点是,在构造函数复杂到难以跟踪正在执行的类型推断的情况下,它使属性的类型更加明显。
这种风格也与你声明和使用诸如dataclasses 之类的东西的方式一致:
from dataclasses import dataclass
@dataclass
class Foo:
x: int
y: Union[int, str]
z: str
# You get an `__init__` for free. Mypy will check to make sure the types match.
# So this type checks:
a = Foo(1, "b", "c")
# ...but this doesn't:
b = Foo("bad", 3.14, 0)
Run Code Online (Sandbox Code Playgroud)
这并不是真正的优点或缺点,只是更多地观察到标准库在某些特定情况下接受了前一种风格。
主要的缺点是这种风格有点冗长:你被迫重复变量名两次(三次,如果你包含__init__参数),并且经常被迫重复两次类型提示(一次在你的变量注释中,一次在在__init__签名中)。
它还在您的代码中打开了一个可能的正确性问题:mypy 永远不会实际检查以确保您已将任何内容分配给您的属性!例如,尽管下面的代码在运行时崩溃,但它会很高兴地进行类型检查:
class Foo:
x: int
def __init__(self, x: int) -> None:
# Whoops, I forgot to do 'self.x = x'
pass
f = Foo(1)
# Type checks, but crashes at runtime!
print(f.x)
Run Code Online (Sandbox Code Playgroud)
后一种风格避免了这些问题:如果你忘记分配一个属性,当你稍后尝试使用它时,mypy 会抱怨它不存在。
后一种风格的另一个主要优点是,您也可以避免在很多时候不添加显式类型提示,特别是如果您只是直接将参数分配给字段。在这些情况下,类型检查器将推断出完全相同的类型。
因此,考虑到这些因素,我个人的偏好是:
__init__.__init__,请使用后一种样式,以减少冗长和遇到“忘记分配属性”错误的几率。__init__的有点难以阅读的风格,请切换回以前的风格。(或者更好的是,只需重构我的代码,这样我就可以保持__init__简单!)当然,您最终可能会以不同的方式权衡这些因素,并提出不同的权衡。
最后一个切线——当你这样做时:
class Foo:
x: int
def __init__(self, x: int) -> None:
# Whoops, I forgot to do 'self.x = x'
pass
f = Foo(1)
# Type checks, but crashes at runtime!
print(f.x)
Run Code Online (Sandbox Code Playgroud)
...您实际上并不是在注释类变量。此时,x 没有值,因此实际上并不作为变量存在。
您创建的唯一内容是annotation,它只是纯元数据并且与变量本身不同。
但如果你这样做:
class Foo:
x: int
Run Code Online (Sandbox Code Playgroud)
...然后您正在创建一个类变量和一个注释。有点令人困惑,虽然您可能正在创建类变量/属性(与实例变量/属性相反),但 mypy 和其他类型检查器将继续假设类型注释旨在专门注释实例属性。
这种不一致在实践中通常无关紧要,特别是如果您遵循避免任何事物可变默认值的一般最佳实践。但是,如果您想做一些奇特的事情,这可能会引起一些意外。
如果你想让 mypy/other 类型检查器理解你的注解是一个类变量注解,你需要使用ClassVar类型:
class Foo:
x: int = 3
Run Code Online (Sandbox Code Playgroud)
如果您想使用Any、Union、 或Optional作为实例变量,您应该对它们进行注释:
from typing import Union
class Foo:
x: Union[int, str]
def __init__(self, an_int: int):
self.x = an_int
def setx(self, a_str: str):
self.x = a_str
Run Code Online (Sandbox Code Playgroud)
否则,您可以使用您认为更容易阅读的内容。mypy 将从 推断类型__init__。
| 归档时间: |
|
| 查看次数: |
1846 次 |
| 最近记录: |