英文:
How can I get a class using multiple inheritance to call all parents' init methods with arguments?
问题
在我的程序中,我使用抽象基类来强制子类具有特定的属性和/或行为。我知道这不是特别符合 Python 的风格,但我的代码复杂度不适用于鸭子类型,而且无论如何,现在改变方法有点晚了。这意味着当一个类需要实现这两个基类中的两个时,我让它们同时继承这两个基类。在这里,我遇到了一个问题,即我的子类需要正确调用两个父类的 init 方法并传递正确的参数,但是 super() 的工作方式似乎不容易实现这一点。我想知道是否有一种方法可以在不直接调用基类的 init 的情况下实现这一点。
我找到了很多关于这个主题的帖子和文章,有些人似乎说这是可能的,有时附有模糊的描述,但我找到的所有来源都没有直接回答如何做到这一点。
假设我有两个基类
class Base1:
def __init__(self, base_1: int, **kwargs):
print(f"Base 1 initialized with base_1={base_1} and **kwargs={kwargs}")
self._base_1: int = base_1
class Base2:
def __init__(self, base_2: int, **kwargs):
print(f"Base 2 initialized with base_2={base_2} and **kwargs={kwargs}")
self._base_2: int = base_2
我想能够实现一个子类,该子类继承自这两个基类,并调用两个 init 方法,如下所示
class Child(Base1, Base2):
def __init__(self, base_1: int, base_2: int):
Base1.__init__(self, base_1=base_1)
Base2.__init__(self, base_2=base_2)
但使用 super()。
我尝试过很多版本。无论是 mro 导致了错误的 init 方法,还是 mro 导致了调用其中一个 init 方法,但没有调用另一个,就像这样:
# 使用这段代码,child = Child(base_1=1, base_2=2) 只会打印:
# "Base 1 initialized with base_1=1 and **kwargs={'base_2': 2}"
class Child(Base1, Base2):
def __init__(self, base_1: int, base_2: int):
super().__init__(base_1=base_1, base_2=base_2)
我理解为什么这段代码会失败,但不知道该怎么办。
有人能解释如何处理这种情况吗?或者是
Base1.__init__(self, base_1=base_1)
Base2.__init__(self, base_2=base_2)
是唯一的方法?
英文:
In my program, I am using abstract base classes to force subclasses to have certain attributes and/or behavior. I am aware this is not particularly pythonic, but the complexity of my code doesn't lend itself to duck-typing, and either way it is a bit late to change the approach. This means that when a class needs to implement two of these base classes, I have them inherit from both of them. There, I am getting the problem that my child class needs to correctly call both parents' init methods with correct arguments, but the way super() seems to work doesn't lend itself to this easily. I would like to know whether there is a way to do this without calling init of the base classes directly.
I have found many posts and articles about this topic, and some seem to say it is possible, sometimes with a vague description, but none of the sources I have found directly answers how to do this.
Let's say I have two base classes
class Base1:
def __init__(self, base_1: int, **kwargs):
print(f"Base 1 initialized with base_1={base_1} and **kwargs={kwargs}")
self._base_1: int = base_1
class Base2:
def __init__(self, base_2: int, **kwargs):
print(f"Base 2 initialized with base_2={base_2} and **kwargs={kwargs}")
self._base_2: int = base_2
I would like to be able to implement a child class that inherits from both base classes that calls both init methods like so
class Child(Base1, Base2):
def __init__(self, base_1: int, base_2: int):
Base1.__init__(self, base_1=base_1)
Base2.__init__(self, base_2=base_2)
but using super().
I have tried many versions. Either the mro leads to the wrong init method or the mro leads to calling one of the init methods, but not the other, like here:
# With this code, child = Child(base_1=1, base_2=2) prints only:
# "Base 1 initialized with base_1=1 and **kwargs={'base_2': 2}"
class Child(Base1, Base2):
def __init__(self, base_1: int, base_2: int):
super().__init__(base_1=base_1, base_2=base_2)
I understand why this code fails, but not what to do about it.
Can someone explain how to handle this scenario? Or is
Base1.__init__(self, base_1=base_1)
Base2.__init__(self, base_2=base_2)
the only way?
答案1
得分: 0
使您的超类使用剩余的kwargs调用super.__init__
:
class Base1:
def __init__(self, base_1: int, **kwargs):
super().__init__(**kwargs)
print(f"Base 1 initialized with base_1={base_1} and **kwargs={kwargs}")
self._base_1: int = base_1
class Base2:
def __init__(self, base_2: int, **kwargs):
super().__init__(**kwargs)
print(f"Base 2 initialized with base_2={base_2} and **kwargs={kwargs}")
self._base_2: int = base_2
class Child(Base1, Base2):
def __init__(self, *, base_1: int, base_2: int):
super().__init__(base_1=base_1, base_2=base_2)
c = Child(base_1=1, base_2=2)
这样,实际self
对象的类的MRO将能够使用单个super调用遍历祖先树。子类构造函数签名中的*,
强制使用命名参数,以防止像Child(1, 2)
这样的调用,这会混淆目标属性并导致在更改超类顺序时出现错误行为。
请注意,您不能传递随机的额外kwargs,因为最后一个super将调用object
构造函数,该构造函数不接受任何[kw]args:
b = Base1(base_1=1, foo=5)
英文:
Make your super classes call super.__init__
with the remaining kwargs:
class Base1:
# def __init__(self, base_1: int, **kwargs): # enforces
def __init__(self, base_1: int, **kwargs):
super().__init__(**kwargs)
print(f"Base 1 initialized with base_1={base_1} and **kwargs={kwargs}")
self._base_1: int = base_1
class Base2:
def __init__(self, base_2: int, **kwargs):
super().__init__(**kwargs)
print(f"Base 2 initialized with base_2={base_2} and **kwargs={kwargs}")
self._base_2: int = base_2
class Child(Base1, Base2):
def __init__(self, *, base_1: int, base_2: int):
super().__init__(base_1=base_1, base_2=base_2)
c = Child(base_1=1, base_2=2)
# Base 2 initialized with base_2=2 and **kwargs={}
# Base 1 initialized with base_1=1 and **kwargs={'base_2': 2}
This way, the mro of the actual self
object's class will be able to traverse the ancestral tree with a single super call.
The *,
in the child class' constructor signature enforces named arguments to disallow calls like Child(1, 2)
which would obfuscate the target attributes and lead to buggy behavior if you were to ever change the order of super classes.
Note that you can't pass random additional kwargs because the last super will call the object
constructor which does not take any [kw]args:
b = Base1(base_1=1, foo=5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __init__
TypeError: object.__init__() takes exactly one argument (the instance to initialize)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论