python class 变量在 __init__ 中改变时可以成为实例变量吗?

Can python class variables become instance variables when altered in __init__?

据我了解 var 是一个 class 变量 这里:

class MyClass:
    var = 'hello'

    def __init__(self):
        print(self.var)

那是一个实例变量:

class MyClass:

    def __init__(self, var):
        self.var = var
        print(self.var)

我遇到了问题,我正在寻找一种方法来使 实例变量 的类型提示成为可能。我当然可以使用 def __init__(self, var: str): 类型提示参数,但这不会影响 实例变量 本身。

然后我注意到在一些描述中(比如)他们使用术语实例变量来表示var,像这样:

class MyClass:
    var : str = 'hello'

    def __init__(self, var : str = None):
        self.var = var if var
        print(self.var)

这确实是解决方案,但它仍然是一个实例变量吗?因为它是在 class 主体中定义的,所以在我的理解中它将是一个 class 变量 。如果您要为 var 使用列表,则对这个 list-var 的所有更改都将在实例上共享。

但在这种情况下不会有问题,因为字符串已被替换并且不会被其他实例共享。但是,如果您称它为 实例变量 对我来说似乎是错误的,而且我不知道我是否应该像这样使用它只是为了让类型提示起作用。

That would be the solution indeed, but is that still an instance variable? Because it is defined in the class body, it would be a class variable in my understanding. [...snip...] However, it seems wrong to me if you call it an instance variable and I don't know if I should use it like this just to have the type hinting working.

无论如何,我也有同样的不适。看起来我们在概念上混合了两个概念只是为了拥有更清晰的类型注释。

不过,我已经就此问题问过 Guido 一两次,看来他确实更喜欢将这些 class 属性视为实例属性。

无论如何,要回答您的核心问题,如果我们这样做:

class Test:
    field1: int
    field2: str = 'foo'

然后...

  1. 符合 PEP 484 和 526 的类型检查器会将此 class 视为:
    1. 它有一个名为 field1
    2. 的实例属性
    3. 它有一个名为 field2 的实例属性,其默认值为 'foo'(根据 PEP 526)。
  2. 在运行时,忽略类型提示,Python 将:
    1. 添加一个名为 field1 的 class annotation 到 Test,但 not a class 属性。 (Class 注释不会自动转换为 class 属性。)
    2. 将名为 field2 的 class 注释 添加到测试以及 class 属性 名为 field2,包含值 'foo'。

所以,它可能会有点混乱。

但不管怎样,这就引出了一个问题:我们如何向类型检查器表明我们希望某个字段真正成为 class 属性?

好吧,事实证明 PEP 484 在最近半年被修改以包含 ClassVar type annotation,正是这样做的。

所以,如果我们想添加一个新的 class 属性,我们可以这样做:

from typing import ClassVar

class Test:
    field1: int
    field2: str = 'foo'
    field3: ClassVar[int] = 3

所以现在,field3 应该被视为默认值为“3”的 class 属性。

(注意:ClassVar 已添加到 typing 用于 Python 3.5.3 -- 如果您使用的是 typing 的旧版本与 Python 3.5,您可以通过 pip 安装 typing_extensions 第三方模块并从那里导入 ClassVar 来获得类型的 "backport"。)

我认为您是否决定采用这种方法是个人喜好。

一方面,Guido 的观点几乎从定义上定义了什么是 "Pythonic" 或什么不是,所以从这个立场来看,采用这个新习语没有问题。此外,语言本身正在缓慢但肯定地转变为采用这种新习语——例如,参见最近接受的 PEP 557,它最终遵循相同的习语对待 class attributes/class注释作为实例属性。

另一方面,这种细微的差异会导致后续问题,这种挥之不去的担忧很难摆脱。在这种情况下,您可以坚持将所有字段设置在 __init__ 内的标准方法。这种方法还有一个好处是使您的代码与 Python 2 和 3.x - 3.5.

兼容

一个中间立场可能就是永远不要以任何方式、形状或形式使用 class 属性 ,而只是坚持使用 class 注释。这有点限制,因为我们不能再给我们的实例变量默认值,但我们现在可以避免完全混淆 class 属性和实例属性。 (如前所述,并在评论中指出,class 注释 未添加为 class 属性。 )