xaav
xaav

Reputation: 7916

Update parent dict in subclass

I have classes like this:

class Parent(object):
    _defaults = {
        'key': 'value',
        'key2': 'value',
    }

class Child(Parent):
    _defaults = {
        'key2': 'value2',
        'key3': 'value2',
    }

When I create the child class, I want _defaults to be set to the parent class _defaults and then updated with the child _defaults. So the actual resolved class should look like this:

class Child(object):
    _defaults = {
        'key': 'value',
        'key2': 'value2',
        'key3': 'value2',
    }

Is this possible with a __metaclass__ constructor or something?

Upvotes: 2

Views: 1086

Answers (3)

Kasravnd
Kasravnd

Reputation: 107287

Using a metaclass you need to do this in __new__ method, in order to apply the changes before instantiation. Since the __new__ method accepts cls, name, bases, namespace, **kwds as argument you can simply access to parent object using bases argument which is a tuple of all base classes then you can simply update your expected attribute.

class MyMetaClass(type):

    def __new__(cls, name, bases, namespace, **kwds):
        bases[0]._defaults.update(namespace['_defaults'])
        result = type.__new__(cls, name, bases, dict(namespace))
        return result


class Parent(object):
    _defaults = {
        'key': 'value',
        'key2': 'value',
    }

class Child(Parent, metaclass=MyMetaClass):
    _defaults = {
        'key2': 'value2',
        'key3': 'value2',
    }

Demo:

c = Child()    
print(type(c).__bases__[0]._defaults)
{'key2': 'value2', 'key3': 'value2', 'key': 'value'}

Note that as @jsbueno mentioned this method has a big problem which is updating the parent class's _defaults attribute at each instantiation. One approach for refusing this problem might be providing the meta class with a flag in order to update the parent only once.

Upvotes: 1

asongtoruin
asongtoruin

Reputation: 10359

Read the dictionary items directly, then update?

class Parent(object):
    _defaults = {
        'key': 'value',
        'key2': 'value',
    }


class Child(Parent):
    _defaults = {k: v for k, v in Parent._defaults.items()}
    _defaults.update({'key2': 'value2',
                      'key3': 'value2'})


testpar = Parent()
test = Child()

print(testpar._defaults)
print(test._defaults)

prints:

{'key2': 'value', 'key': 'value'}
{'key3': 'value2', 'key2': 'value2', 'key': 'value'}

in both 2.7 and 3.5

Upvotes: 1

mgilson
mgilson

Reputation: 309889

You can accomplish this using a collections.ChainMap

import collections

class Child(Parent):
    _defaults = collections.ChainMap({
        'key2': 'value2',
        'key3': 'value2',
    }, Parent._defaults)

Of course, you could write a metaclass to combine these things for you, but it would be tricky to figure out (in general) which maps should be combined with the maps on the superclasses and which should override...

Upvotes: 3

Related Questions