Изменить значение аргумента конструктора по умолчанию (унаследованное от родительского класса) в подклассе

У меня есть Parent класс со значением по умолчанию для атрибута arg2, Я хочу создать подкласс Child который имеет другое значение по умолчанию для того же атрибута. Мне нужно использовать *args а также **kwargs в Child,

Я попробовал следующее, но это не работает:

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'

3 ответа

Решение

Вам нужно установить значение по умолчанию в 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)

Вместо этого вам лучше указать все значения по умолчанию:

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

Вы на самом деле изменили подпись класса. В основном с:

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)

Это (к сожалению) не СУХОЙ (не повторяйте себя), как вы, вероятно, хотели бы - вы должны указать 'something' дважды. Вы можете превратить его в глобальную константу или изменить сигнатуру Parent.__init__,

В качестве альтернативы, вы могли бы сделать кучу самоанализа для работы с сигнатурой родительского класса, чтобы убедиться, что вы правильно передаете правильные аргументы - но я очень сомневаюсь, что оно того стоит.

Меня не удовлетворило ни одно из решений, и я придумал следующее. Он вводит значения по умолчанию как атрибуты класса, которые загружаются, если значение по умолчанию равно None:

      #!/usr/bin/env python3

class Parent:
    _arg1_default = 'something'
    _arg2_default = 'old default value'

    def __init__(self, arg1=None, arg2=None):
        if arg1 is None:
            arg1 = self._arg1_default
        if arg2 is None:
            arg2 = self._arg2_default

        self.arg1 = arg1
        self.arg2 = arg2

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


class Child(Parent):
    _arg2_default = 'new value'

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)


if __name__ == '__main__':
    print('Call Parent without arguments (use defaults)')
    parent = Parent()
    print('Call Child without arguments (use defaults)')
    child = Child()
    print('Call 2nd Child with custom arguments ("one", "two")')
    child2 = Child('one', 'two')
    print('Query arg2 of 2nd child')
    print(child2.arg2)

Урожайность:

      Call Parent without arguments (use defaults)
arg1: something
arg2: old default value
Call Child without arguments (use defaults)
arg1: something
arg2: new value
Call 2nd Child with custom arguments ("one", "two")
arg1: one
arg2: two
Query arg2 of 2nd child
two   
Другие вопросы по тегам