【问题标题】:Change default constructor argument value (inherited from parent class) in subclass更改子类中的默认构造函数参数值(从父类继承)
【发布时间】:2017-05-28 04:01:14
【问题描述】:

我有一个Parent 类,其属性arg2 的默认值。我想创建一个子类Child,它对同一属性具有不同的默认值。 我需要在Child 中使用*args**kwargs

我尝试了以下方法,但它不起作用:

class Parent(object):
    def __init__(self, arg1='something', arg2='old default value'):
        self.arg1 = arg1
        self.arg2 = arg2

        print('arg1:', self.arg1)
        print('arg2:', self.arg2)

class Child(Parent):
    def __init__(self, *args, **kwargs):
        super(Child, self).__init__(*args, **kwargs)
        self.arg2 = kwargs.pop('arg2', 'new value')

这不起作用。事实上,我得到:

>>> c = Child()
arg1: something
arg2: default value # This is still the old value
>>> c.arg2
'new value' # Seems more or less ok

>>> c = Child('one', 'two')
arg1: one
arg2: two
>>> c.arg2
'new value' # This is wrong, it has overridden the specified argument 'two'

【问题讨论】:

  • 这是必然的;在第二个示例中,arg2args[1],而不是 kwargs['arg2']
  • @jonrsharpe 是的,我似乎找不到修复它的好方法

标签: python constructor arguments subclass


【解决方案1】:

您需要在kwargs 中设置默认值,然后再将其传递给super();这很棘手,因为您需要确保 args 中也没有相同的值:

class Child(Parent):
    def __init__(self, *args, **kwargs):
        if len(args) < 2 and 'arg2' not in kwargs:
            kwargs['arg2'] = 'new value'
        super(Child, self).__init__(*args, **kwargs)

这依赖于知道有多少参数需要填写。您必须使用 super().__init__ 的自省才能在一般情况下工作:

from inspect import getargspec

class Child(Parent):
    def __init__(self, *args, **kwargs):
        super_init = super().__init__
        argspec = getargspec(super_init)
        arg2_index = argspec.args.index('arg2') - 1  # account for self
        if len(args) < arg2_index and 'arg2' not in kwargs:
            kwargs['arg2'] = 'new value'
        super(Child, self).__init__(*args, **kwargs)

你最好指定 all 默认值:

class Child(Parent):
    def __init__(self, arg1='something', arg2='new value'):
        super(Child, self).__init__(arg1=arg1, arg2=arg2)

【讨论】:

  • 这不是有两个arg2 的值的风险吗?
  • 这实际上对Child('one', 'two') 失败:TypeError: __init__() got multiple values for argument 'arg2'
  • @jonrsharpe:是的,有这个风险,我会调整的。
【解决方案2】:

您实际上已经更改了类的签名。基本上,有:

def foo(a=1, b=2):
   ...

您可以按位置调用,按关键字调用:

foo(2, 3)
foo(a=2, b=3)

与:

def bar(**kwargs):
   ...

您不能再使用位置参数调用:

bar(2, 3)  # TypeError!

你的实际代码有额外的复杂性,因为你有 *args 在那里吃掉你所有的位置参数。


我能给你的最可靠的建议是在你重写方法时保留签名:

class Child(Parent):
    def __init__(self, arg1='something', arg2='new value'):
        super(Child, self).__init__(arg1=arg1, arg2=arg2)

这(不幸的是)不是您可能想要的 DRY(不要重复自己)——您必须指定 'something' 两次。你可以把它变成一个全局常量,或者改变Parent.__init__的签名。

或者,您可以进行大量内省以使用父类的签名,以确保您以正确的方式传递正确的参数 - 但我非常怀疑它是否值得。

【讨论】:

    猜你喜欢
    • 2011-05-20
    • 2011-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-24
    • 1970-01-01
    • 2011-12-08
    • 2018-09-07
    相关资源
    最近更新 更多