【问题标题】:control initialize order when Python dataclass inheriting a classPython数据类继承类时控制初始化顺序
【发布时间】:2019-07-22 11:46:39
【问题描述】:

我所知道的
Python 数据类允许使用数据类或类进行继承。 在最佳实践中(以及在其他语言中),当我们进行继承时,应该首先调用初始化。在 Python 中是:

def __init__(self):
    super().__init__()
    ...

我在做什么
由于数据类是在 Python 3.7 中引入的,我正在考虑用数据类替换我的所有类。 使用数据类,它的好处之一是为您生成__init__。当数据类需要继承基类时,这并不好——例如:

class Base:
    def __init__(self):
        self.a = 1

@dataclass
class Child(Base):
    a:int
    def __post_init__(self):
        super().__init__() 

我的问题
问题是我们必须将超级初始化调用放在__post_init__ 中,实际上它是在 数据类的 init 之后调用的。
缺点是我们失去了约定契约,初始化混乱导致我们无法覆盖超类的属性。

可以通过__pre_init__的概念来解决。我已经阅读了该文件,并没有看到与该概念有任何关系。我错过了什么吗?

【问题讨论】:

  • we can not override attributes of super classes 是什么意思?你想在子类中有一个与父类同名的属性吗?比如class A: some_name: intclass B(A): some_name: int?
  • @Arne 是的,同名属性。在 Scala 或 Java 中,父级总是先于子级初始化。并且子属性总是可以在初始化之后覆盖父属性。
  • 好的,我想我明白了。但我认为这种原则不适用于手头的问题。我在my answer 中多用了几句。

标签: python inheritance python-dataclasses


【解决方案1】:

怎么样:

from dataclasses import dataclass


class Base:
    def __init__(self, a=1):
        self.a = a


@dataclass
class Child(Base):

    def __post_init__(self):
        super().__init__()


ch = Child()

【讨论】:

  • 这与问题中的内容有何不同?
  • 运行问题代码并运行我的代码,您会看到我的版本在您实例化孩子时不需要您提供参数。除此之外没有太大区别。
  • 我喜欢这个,但它阻止了超类的覆盖属性......这就是@WeiChing 在原始问题中所指的。 IOW、ch = Child(a=3)ch = Child(3) 都失败了。
【解决方案2】:

实际上在__init__之前调用了一个方法:它是__new__。所以你可以这样做:在Child.__new__ 中调用Base.__init__。我不能说这是一个好的解决方案,但如果您有兴趣,这里有一个工作示例:

class Base:
    def __init__(self, a=1):
        self.a = a


@dataclass
class Child(Base):
    a: int

    def __new__(cls, *args, **kwargs):
        obj = object.__new__(cls)
        Base.__init__(obj, *args, **kwargs)
        return obj


c = Child(a=3)
print(c.a)  # 3, not 1, because Child.__init__ overrides a

【讨论】:

  • 这样做的一个缺点是您必须使用默认值 c = child(a=1) 使客户端代码混乱,因为 c = child() 给出错误 TypeError: __init__() missing 1 required positional argument: 'a'。这样一来,您将失去在 def __init__(self, a=1) 中设置默认值的好处。
【解决方案3】:

在最佳实践中 [...],当我们进行继承时,应首先调用初始化。

这是一个合理的最佳实践,但在数据类的特定情况下,它没有任何意义。

调用父构造函数有两个原因,1) 实例化要由父构造函数处理的参数,2) 运行父构造函数中需要在实例化之前发生的任何逻辑。

Dataclasses 已经为我们处理了第一个:

 @dataclass
class A:
    var_1: str

@dataclass
class B(A):
    var_2: str

print(B(var_1='a', var_2='b'))  # prints: B(var_1='a', var_2='b')
# 'var_a' got handled without us needing to do anything

第二个不适用于数据类。其他类可能在其构造函数中做各种奇怪的事情,但数据类只做一件事:它们将输入参数分配给它们的属性。如果他们需要做其他事情(__post_init__ 无法处理),您可能正在编写一个不应该是数据类的类。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多