Jiloc
Jiloc

Reputation: 3658

Best practice: update/extend subclass class attribute

This is just a little curiosity. I usually find myself updating a class attribute inherited in a subclass and I'd like to know how others deals with it. I know that I can't update them in the __init__: since it's a class attribute I would update it to the superclass instances as well. So I usually do something like this:

class A(object):
    attribute = {
        1: 'a',
        ...,
        5: 'e',
    }

class B(A):
    attribute = {
        1: 'a',
        ...,
        5: 'e',
        6: 'f',
    }

But, since I like to follow the DRY principle as much as possible and I'd like to know if someone use a more elegant way to do this without completely copy and paste the attribute.

As asked this is a concrete example with Django forms:

class LoginForm(AuthenticationForm):

    username = forms.EmailField(label=_('Email'))

    error_messages = {
        'inactive': _('This account is inactive.'),
        'invalid_login': _('Please enter a correct username and password. '
                           'Note that both fields may be case-sensitive.'),
        'unconfirmed': _('Your account is not confirmed yet. Please '
                         'check your email and follow the instructions '
                         'in order to activate it.'),
    }

    def confirm_login_allowed(self, user):
        super(LoginForm, self).confirm_login_allowed(user)
        if not user.confirmed:
            raise forms.ValidationError(
                self.error_messages['unconfirmed'],
                code='unconfirmed')

The error_messages attribute is inherited from django.contrib.auth.forms.AuthenticationForm but I wanted to add the 'unconfirmed' key so I had to copy and paste the 'inactive' and 'invalid_login' keys too.

Upvotes: 1

Views: 109

Answers (2)

Ethan Furman
Ethan Furman

Reputation: 69120

If you do put it in __init__ then every instance of the subclass (B in this case), will see the update. The only time you would not is if you specify the class directly:

B.attribute[6]
# KeyError

One way without using __init__ is to:

class B(A):
    attribute = A.attribute.copy()
    attribute.update({
          6: 'f',
          7: 'g',
          })

print(B().attribute) # --> {1:'a', ..., 5:'e', 6:'f', 7:'g'}
print(A().attribute) # --> {1:'a', ..., 5:'e'}

The point to remember is that a class's code is executed as it is created, so you can use normal Python to make adjustments.

Upvotes: 3

martineau
martineau

Reputation: 123491

This seems to work:

class A(object):
    attribute = {
        1: 'a',
        5: 'e',
    }

class B(A):
    attribute = A.attribute.copy()
    attribute[6] = 'f'

print(A.attribute)  # -> {1: 'a', 5: 'e'}
print(B.attribute)  # -> {1: 'a', 5: 'e', 6: 'f'}

Upvotes: 1

Related Questions