键入注释实例属性:在 init 或 body 中?

问题描述 投票:0回答:2

让我们考虑以下两种语法变体:

class Foo:

    x: int

    def __init__(self, an_int: int):
        self.x = an_int

还有

class Foo:

    def __init__(self, an_int: int):
        self.x = an_int

显然以下代码在两种情况下都会引发 mypy 错误(这是预期的):

obj = Foo(3)
obj.x.title()  # this is a str operation

但我真的很想执行这个契约:我想明确指出 x 是每个

Foo
对象的实例变量。那么应该首选哪种语法,为什么?

python python-typing mypy
2个回答
24
投票

这最终是个人喜好的问题。要使用另一个答案中的示例,请同时执行以下操作:

class Foo:
    x: Union[int, str]

    def __init__(self, an_int: int) -> None:
        self.x = an_int

...并且正在做:

class Foo:
    def __init__(self, an_int: int) -> None:
        self.x: Union[int, str] = an_int

...类型检查器将以完全相同的方式处理。

执行前者的主要优点是,在构造函数复杂到难以跟踪正在执行的类型推断的情况下,它使属性的类型更加明显。

这种风格也与你声明和使用像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)

这并不是真正的赞成或反对,只是更多地观察到标准库在某些特定情况下接受了前一种风格。

主要缺点是这种风格有点冗长:您被迫重复变量名称两次(如果包含

__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)

后一种风格避免了这些问题:如果你忘记分配一个属性,当你稍后尝试使用它时,mypy会抱怨它不存在。

后一种风格的另一个主要优点是,您也可以在很多时候不添加显式类型提示,特别是当您只是将参数直接分配给字段时。在这些情况下,类型检查器将推断出完全相同的类型。


因此考虑到这些因素,我个人的偏好是:

  1. 如果我只想要一个简单的、类似记录的对象并自动生成
    __init__
    ,请使用数据类(并通过代理,前一种样式)。
  2. 如果我觉得数据类太过分或者需要编写自定义
    __init__
    ,请使用后一种风格,以减少冗长和遇到“忘记分配属性”错误的可能性。
  3. 如果我有足够大且复杂的
    __init__
    ,有点难以阅读,请切换回以前的样式。 (或者更好的是,只需重构我的代码,这样我就可以保持
    __init__
    简单!)

当然,您最终可能会以不同的方式权衡这些因素,并得出一组不同的权衡。


最后一个切线——当你这样做时:

class Foo:
    x: int

...您实际上并没有注释类变量。此时,x 没有值,因此实际上并不作为变量存在。

您唯一要创建的是一个注释,它只是纯元数据,与变量本身不同。

但是如果你这样做:

class Foo:
    x: int = 3

...然后您正在创建一个类变量和一个注释。有点令人困惑的是,虽然您可能正在创建 class 变量/属性(而不是 instance 变量/属性),但 mypy 和其他类型检查器将继续假设类型注释旨在专门注释 instance 属性。

这种不一致在实践中通常并不重要,特别是如果您遵循避免任何内容可变默认值的一般最佳实践。但如果你想做一些奇特的事情,这可能会带来一些惊喜。

如果您希望 mypy/其他类型检查器了解您的注释是

类变量注释,您需要使用 ClassVar

 类型:

# Import this from 'typing_extensions' if you're using Python 3.7 or earlier from typing import ClassVar class Foo: x: ClassVar[int] = 3
    

3
投票
如果您想使用

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

否则您可以使用您认为更容易阅读的内容。 mypy 将从

__init__

 推断类型。

© www.soinside.com 2019 - 2024. All rights reserved.