qouify
qouify

Reputation: 3920

Default values in kwargs with class inheritance

I have two classes A and B(A). Their constructor accept the style dictionary as a keyword argument.

For A objects I want, by default, the string green to be associated to style['color'].

For B objects color defaults to red.

I came up with this solution but find it rather clumsy. Is there a more elegant way to do this?

class A:
    def __init__(self, **kwargs):
        style = kwargs.pop('style', {})
        if 'color' not in style:
            style['color'] = 'green'
        print(style)

class B(A):
    def __init__(self, **kwargs):
        style = kwargs.pop('style', {})
        if 'color' not in style:
            style['color'] = 'red'
        super().__init__(style=style, **kwargs)

A()  #  {'color': 'green'}
B()  #  {'color': 'red'}
B(style={'color': 'blue'})  #  {'color': 'blue'}

Upvotes: 1

Views: 247

Answers (2)

costaparas
costaparas

Reputation: 5237

Try the following instead.

It introduces a helper method to the base class called default_kwarg.

This can be called in both cases (superclass and subclass) to set the default value of the color key.

This approach also enables the style dict to contain other key/value pairs. I've added an extra example at the end that demonstrates this.

class A:
    def default_kwarg(self, arg, default):
        arg = arg.pop('style', {})
        if 'color' not in arg:
          arg['color'] = default
        return arg

    def __init__(self, **kwargs):
        style = self.default_kwarg(kwargs, 'green')
        print(style)

class B(A):
    def __init__(self, **kwargs):
        kwargs['style'] = self.default_kwarg(kwargs, 'red')
        super().__init__(**kwargs)

A()  #  {'color': 'green'}
B()  #  {'color': 'red'}
B(style={'color': 'blue'})  #  {'color': 'blue'}
B(style={'something': 'else'})  #  {'something': 'else', 'color': 'red'}

You could also generalize this helper method quite easily to apply defaults to other keys in the dict.

Upvotes: 1

Axe319
Axe319

Reputation: 4365

This looks slightly cleaner but it still feels a bit hacky:

class A:
    def __init__(self, **kwargs):
        self.style = kwargs.get('style', {'color': 'green'})
        print(self.style)

class B(A):
    def __init__(self, **kwargs):
        kwargs['style'] = kwargs.get('style', {'color': 'red'})
        super().__init__(**kwargs)

A()  #  {'color': 'green'}
B()  #  {'color': 'red'}
B(style={'color': 'blue'})

If style is actually self.style which I'm assuming it is, you can relegate the assignment to the parent class since it will always get there with super() anyway.

This assumes that aren't attempting to pull 'style' from kwargs afterwards. You shouldn't need to since it's already assigned to self.style.

You probably noticed that it would also fail if you would pass in style but not assign a 'color' to it. If you're planning on doing this, you would probably be better off just setting color as a default keyword argument.

class A:
    def __init__(self, color='green', **kwargs):
        self.style = kwargs.get('style', {'color': color})
        self.style['color'] = self.style.get('color', color)
        print(self.style)

class B(A):
    def __init__(self, color='red', **kwargs):
        kwargs['style'] = kwargs.get('style', {'color': color})
        super().__init__(color=color, **kwargs)

A()  #  {'color': 'green'}
B()  #  {'color': 'red'}
B(style={'font': 'Times New Roman'})  #  {'color': 'red'}
B(style={'color': 'blue'})

Upvotes: 1

Related Questions