Изменить значение аргумента конструктора по умолчанию (унаследованное от родительского класса) в подклассе
У меня есть 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