Search code examples
pythonconstructorargumentssubclass

Change default constructor argument value (inherited from parent class) in subclass


I have a Parent class with a default value for the attribute arg2. I want to create a subclass Child which has a different default value for the same attribute. I need to use *args and **kwargs in Child.

I tried the following, but it is not working:

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')

This is not working. In fact, I get:

>>> 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'

Solution

  • You need to set the default in kwargs before passing it on to super(); this is tricky as you need to ensure that the same value is not already in args too:

    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)
    

    This relies on knowing how many arguments are there to fill however. You'd have to use introspection of super().__init__ for this to work in the general case:

    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)
    

    You'd be much better off specifying all defaults instead:

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