类型注释类变量:是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 type-hinting mypy
2个回答
1
投票

如果要为实例变量使用AnyAnyUnion,则应为它们添加注释:

Union

否则,您可以使用任何您认为更容易阅读的内容。 mypy将从Optional推断类型。


1
投票

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

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

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

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

此样式还与您声明和使用__init__之类的内容一致:

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签名中输入)。

它还会在您的代码中带来一个可能的正确性问题:mypy永远不会实际检查以确保您已为属性分配任何内容!例如,尽管以下代码在运行时崩溃,但它们仍将很高兴地键入check:

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)

后一种样式避免了这些问题:如果您忘记分配属性,mypy将在以后尝试使用该属性时抱怨它不存在。

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


因此,鉴于这些因素,我个人的偏好是:

  1. 如果我只想要一个具有自动生成的__init__的简单,类似记录的对象,请使用数据类(并通过代理使用以前的样式)。>
  2. 如果我感觉数据类过大或需要编写自定义__init__,以减少冗长性和出现“忘记分配属性”错误的可能性,请使用后一种样式。
  3. 如果我有足够大且复杂的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) ,这有点难以阅读,请切换回以前的样式。 (或者更好,只是重构我的代码,这样我就可以使__init__简单!)
  4. 当然,您最终可能会权衡这些因素,并得出不同的权衡。


一个最终切线-操作时:

__init__

...您实际上没有在注释class variable

。此时,x没有值,因此实际上不作为变量存在。

您正在创建的唯一东西是annotation

,它只是纯元数据,与变量本身不同。

但是如果您这样做:

__init__

...然后您are

创建一个类变量和一个注释。令人困惑的是,尽管您可能正在创建class变量/属性(而不是instance变量/属性),但是mypy和其他类型检查器将继续进行操作,假设类型注释旨在专门注释< [实例属性。这种不一致在实践中通常并不重要,特别是如果您遵循避免对任何事物使用可变默认值的一般最佳实践,则尤其如此。但是,如果您想做一些特别的事,可能会引起一些意外。

如果您希望mypy /其他类型检查器将您的注释理解为

类变量注释

,则需要使用__init__类型:
class Foo: x: int
© www.soinside.com 2019 - 2024. All rights reserved.