2017-01-12 9 views
1

У меня есть класс 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' 
+0

Это обязательно произойдет; во втором примере 'arg2' -' args [1] ', а не' kwargs ['arg2'] '. – jonrsharpe

+0

@jonrsharpe Да, и я не могу найти хороший способ исправить его. –

ответ

2

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

Не рискет ли это иметь два значения для 'arg2', передавая его как по позициям *, так и по ключевому слову? – jonrsharpe

+0

Это не работает для 'Child ('one', 'two')': 'TypeError: __init __() получил несколько значений для аргумента 'arg2'' –

+1

@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__.

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