更改在基类__init__中调用的函数的签名

6
A.__init__中,我调用了self.func(argument):
class A(object):
    def __init__(self, argument, key=0):
        self.func(argument)
    def func(self, argument):
        #some code here

我想要修改BA.func的签名。通过A.__init__B.__init__调用了B.func

class B(A):
    def __init__(self, argument1, argument2, key=0):
        super(B, self).__init__(argument1, key)  # calls A.__init__
    def func(self, argument1, argument2):
        #some code here

明显,这个方法不起作用是因为B.func的签名需要两个参数,而A.__init__只提供了一个参数。我该怎么解决这个问题?或者说我的类设计有什么问题吗? keyA.__init__的默认参数。 argument2并不是针对key的。 argument2B使用但A没有的额外参数。B还使用key,并为其设置了默认值。
另一个限制是我不想改变A.__init__的签名。 key通常会是0。所以我希望允许用户写A(arg)而不是A(arg, key=0)

1
根据 super 调用,这是 Python2。 - wim
@wim:或者多语言代码,或者不了解0参数形式的人。 - Martijn Pieters
3
在B中,我想改变func的签名 -听起来你要破坏里氏替换原则。你确定这是一个好主意吗?另外,在__init__中调用应该被覆盖的方法是危险的,因为这些方法可能依赖于尚未发生的子类初始化。 - user2357112
1
答案中有些混淆,我认为。当调用super(B, self).__init__(argument1, argument2)时,您是否希望将argument1argument2传递给B.func()?在这方面,A.__init__()中的key=0的作用是什么,它是否应该捕获argument2的值? - Martijn Pieters
我正在使用Python2编写这个。 - Swetabh
显示剩余3条评论
3个回答

6
一般来说,子类方法的签名变化会破坏预期,即子类上的方法实现与父类上的方法相同。但是,您可以重新设计A.__init__方法以允许任意额外参数,并将这些参数传递给self.func()方法。
class A(object):
    def __init__(self, argument, *extra, **kwargs):
        key = kwargs.get('key', 0)
        self.func(argument, *extra)
    # ...

class B(A):
    def __init__(self, argument1, argument2, key=0):
        super(B, self).__init__(argument1, argument2, key=key)
    # ...
super(B, self).__init__()的第二个参数被捕获到extra元组中,并在除argument之外应用于self.func()
在Python 2中,为了能够使用extra,您需要切换到使用**kwargs,否则key总是会捕获第二个位置参数。确保使用key=keyB传递key
在Python 3中,您不受此限制;将*args放在key=0之前,并且只在调用中使用key作为关键字参数。
class A(object):
    def __init__(self, argument, *extra, key=0):
        self.func(argument, *extra)

我建议给func()函数添加一个*extra参数,这样它在AB之间的接口基本上不会改变;对于A,它忽略除第一个传入参数外的任何内容,对于B,它忽略除前两个参数外的任何内容:

class A(object):
    # ...
    def func(self, argument, *extra):
        # ...

class B(A):
    # ...
    def func(self, argument1, argument2, *extra):
        # ...

Python 2演示:

>>> class A(object):
...     def __init__(self, argument, *extra, **kwargs):
...         key = kwargs.get('key', 0)
...         self.func(argument, *extra)
...     def func(self, argument, *extra):
...         print('func({!r}, *{!r}) called'.format(argument, extra))
...
>>> class B(A):
...     def __init__(self, argument1, argument2, key=0):
...         super(B, self).__init__(argument1, argument2, key=key)
...     def func(self, argument1, argument2, *extra):
...         print('func({!r}, {!r}, *{!r}) called'.format(argument1, argument2, extra))
...
>>> A('foo')
func('foo', *()) called
<__main__.A object at 0x105f602d0>
>>> B('foo', 'bar')
func('foo', 'bar', *()) called
<__main__.B object at 0x105f4fa50>

我没有点踩,但鼓励采用不同的设计实践可能会有所帮助。第一个类可以使用super来引用该方法的第一个类实现吗? - Noctis Skytower
@NoctisSkytower:不确定你的意思?super(A, self).func(argument)是行不通的,因为搜索从MRO中的下一个类开始。 - Martijn Pieters
@MartijnPieters 一个快速的实验让我找到了一种帮助基类的解决方案。 - Noctis Skytower

0

看起来你的设计存在问题。以下方法可能可以解决你的特定情况,但似乎会进一步延续不良设计。请注意直接调用Parent.method

>>> class Parent:
    def __init__(self, a, b=None):
        Parent.method(self, a)
        self.b = b
    def method(self, a):
        self.location = id(a)


>>> class Child(Parent):
    def __init__(self, a):
        super().__init__(a, object())
    def method(self, a, b):
        self.location = id(a), id(b)


>>> test = Child(object())

请考虑在您重写的方法的第二个参数中添加默认参数。否则,请重新设计您的类和调用结构。重新组织可能会消除问题。

1
现在Child.method()永远不会被调用,而额外的参数object()b捕获。 - Martijn Pieters
我的假设可能是不正确的,但是我认为Child.method不应该从Parent.__init__中调用。根据作者的意图,Child.method可能被设计为在其他地方内部或外部调用。如果父类和子类方法都不应该被外部调用,则在两个类中将method重命名为__method可能是解决方法。没有进一步的上下文,很难知道最佳解决方案是什么。甚至可以选择将其重命名为_method以表示内部使用。正确的答案并不明显。 - Noctis Skytower
1
从在B.__init__中将argument1argument2用作局部变量以及用作B.func()的名称来看,可以暗示出期望从A.__init__()中调用B.func()。我已经在问题上进行了评论以澄清这一点。 - Martijn Pieters
@MartijnPieters:你说得对。我打算在类B的对象被初始化时调用B.func() - Swetabh
@NoctisSkytower:你能详细说明一下为什么你认为这是糟糕的设计吗?我的用例是func创建一个与类相关的对象。一种相关表格。这个相关对象可能不同 - 有时它有2个表格而不是1个。我希望这能澄清我的意图。 - Swetabh
@Swetabh 可以通过从 __init__ 中调用一个或多个内部方法来消除问题。然后,您可以在子类中覆盖所需的任何功能。如果只需要编写一两行代码,则此设计可能还允许在子类中甚至不需要使用 super。但是,我同意 Martijn Pieters 的观点,支持可变数量的参数可能是最好的方法。或者,您可以将数组/可迭代对象传递给函数,并要求调用者以这种方式打包数据。 - Noctis Skytower

0

实际上,我会在 A 的 __init__ 中添加一个额外的布尔参数来控制函数的调用,并且仅从 B 的 __init__ 传递 False

class A(object):
    def __init__(self, argument, key=0, call_func=True):
        if call_func:
            self.func(argument)

class B(A):
    def __init__(self, argument):
        argument1, argument2 = argument, 'something else'
        super(B, self).__init__(argument1, argument2, call_func=False)

那么为什么要在 B 中覆盖 func() 呢?而且你仍然在 key 中捕获了 argument2 - Martijn Pieters
由于我不知道参数1、参数2和关键字的用法,我没有试图理解代码,但据我所知,他需要在B中覆盖func(),但他遇到了一个问题,即func()作为A的init的一部分被调用! - 0xcurb
@0xcurb:我想调用func,因为它初始化了一个我需要与class A关联的对象。我不想在A__init__内重写代码。在我的情况下,A还调用了其父类的构造函数。 - Swetabh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接