为内置类型的多重继承子类构造函数添加可选参数?

4 投票
1 回答
2589 浏览
提问于 2025-04-16 04:21

我对多重继承的理解还不够深刻。我想创建一个父类,这个父类的 __init__ 方法可以接受一个可选的命名参数,同时它的子类也要继承一些内置类型。但可惜的是,我似乎不知道该怎么做:

>>> class Super(object):
    name = None
    def __init__(self, *args, name=None, **kwargs):
        self.name = name
        super().__init__(self, *args, **kwargs)

>>> class Sub(Super, int):
    pass

>>> Sub(5)
5

>>> Sub(5, name="Foo")
Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    Sub(5, name="Foo")
TypeError: 'name' is an invalid keyword argument for this function

(我也试过不使用 super() 调用,但结果还是一样。)

也许有更懂多重继承的人可以给我一些指点?

更新:

这是我最终得到的解决方案,基于 Alex的回答

这个方法还有点不太规范(因为在构造过程中改变了 __new__ 的签名),但只要父类在子类的MRO(方法解析顺序)中出现在内置类型之前,这个方法就能正常工作。而且这样定义 __new__ 让我可以创建与不同内置类型兼容的子类,而不需要为每个子类单独添加 __new__ 方法。

>>> class Super(object):
    name = None
    def __new__(cls, *args, name=None, **kwargs):
        inner = super().__new__(cls, *args, **kwargs)
        inner.name = name
        return inner

>>> class Sub(Super, int):
    pass

>>> Sub(5)
5

>>> Sub(5, name="Foo")
5

>>> _.name
'Foo'

1 个回答

3

你不能随便给一个父类(也就是当前类型的“上一个类型”)传递任意的参数(无论是位置参数还是命名参数)。大多数类型和类都不接受随意的参数,这样做是有很好的理由的。引用《Python之禅》中的一句话:

Errors should never pass silently.
Unless explicitly silenced.

在大多数情况下,调用比如 int(name='booga') 当然会出错。

如果你希望你那个奇怪命名的 class Super 能够“传递”任意参数,你还得确保它后面所有作为基类的类都能处理这些参数。比如说,int 可以接受一个参数(或者正好两个:一个字符串和一个进制),所以如果你特别希望 class Sub 能够同时继承这个能传递参数的 Superint,你就得考虑这个问题,例如:

class Int(int):
    def __new__(cls, *a, **k):
        return int.__new__(Int, a[0] if a else 0)

注意,你必须重写 __new__,而不是 __init__(如果你也重写后者也没关系,但其实没什么必要):int 是不可变的,所以值必须在 __new__ 时就设置好。

现在,像这样的代码:

>>> class X(Super, Int): pass
... 
>>> X(23, za='zo')
23
>>> 

就能正常工作了。但要注意,X 必须从 Int(我们重写过的 int)继承,而不是直接从 int 继承,因为后者有一个严格的 __new__ 方法!

撰写回答