【问题标题】:Can python class variables become instance variables when altered in __init__?在 __init__ 中更改时,python 类变量可以成为实例变量吗?
【发布时间】:2018-01-16 03:09:48
【问题描述】:

据我了解var 是一个类变量

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): 输入参数,但这不会影响 instance 变量 本身。

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

class MyClass:
    var : str = 'hello'

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

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

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

【问题讨论】:

  • 用实例变量覆盖类变量是一种反模式。你应该避免使用它。 def __init__(self, var: str): 有什么问题?它向用户表明var 应该是一个字符串。
  • 根据我的翻译,这是SyntaxError
  • @Goyo 这是在 Python 3.5 中实现的
  • 我正在使用python 3.6
  • @James 问题是,__init__ 方法中的本地 var 将被输入提示,而不是实例变量本身。当我在另一种方法中使用self.var 时,解释器和IDE 不会知道这应该是一个字符串

标签: python type-hinting


【解决方案1】:

这确实是一个解决方案,但这仍然是一个实例变量吗?因为它是在类体中定义的,所以在我的理解中它将是一个类变量。 [...snip...] 但是,如果您将其称为实例变量,我不知道是否应该像这样使用它只是为了使类型提示起作用。

对于它的价值,我也有同样的不适。似乎我们在概念上混合了两个概念,只是为了有更清晰的类型注释。

但是,我已经向 Guido 询问过一两次关于此的问题,他似乎确实更喜欢将这些类属性视为实例属性。

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

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

那么……

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

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

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

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

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

from typing import ClassVar

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

所以现在,field3 应该被视为一个默认值为 '3' 的类属性。

(注意:对于 Python 3.5.3,ClassVar 已添加到 typing - 如果您使用的是与 Python 3.5 捆绑在一起的旧版 typing,您可以通过安装typing_extensions 第三方模块通过 pip 并从那里导入 ClassVar。)

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

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

另一方面,我们很难摆脱这种微妙的差异会导致问题的烦恼。在这种情况下,您可以坚持将所有字段设置在 __init__ 中的标准方法。这种方法还具有使您的代码与 Python 2 和 3.x - 3.5 兼容的好处。

中间立场可能是干脆从不使用类属性,以任何方式、形状或形式,只坚持使用类注解。这有点限制,因为我们不能再给我们的实例变量默认值,但我们现在可以完全避免将类属性与实例属性混为一谈。 (如前所述,并在 cmets 中指出,类 annotations 不作为类 attributes 添加。)

【讨论】:

  • field1: int 不创建类属性。注释不会创建属性;您需要实际分配一个值来创建属性。
  • 此外,3.6 之前的版本不存在变量注释,因此即使您安装了反向端口,您实际上也无法对 3.6 之前的 ClassVar 执行任何操作。
  • @user2357112 好的,当然,但是执行field1: int = 3 确实会创建一个类属性,但根据PEP 526,它应该被视为具有默认参数的实例字段——我的首要观点正是因为这样的事情,才会有一些奇怪的混合。另外,我相信foo = 3 # type: ClassVar[int] 是合法的,因此拥有该反向移植有一些价值。 (事实上​​,ClassVar 实际上是在 Python 3.5.3 中添加的,而不是 Python 3.6。)
  • 完全同意混合的奇怪之处。
  • @user2357112 无论如何,我澄清了类属性与类注释的事情以解决您的第一个反对意见。
猜你喜欢
  • 2011-05-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多