Combining a dict class attribute through a class hierarchy

I am trying to make the following code work. I would like to create a class attribute, which is a dictionary of default values ​​that is automatically updated from children to parents through the class hierarchy. Ideally, I would like to do this using magic in the parent object so that child objects can simply redefine values ​​as needed. I would also be open to suggestions on how to reverse engineer this if there is an idiom for this kind of thing.

class A(object): DEFAULTS = {'a': 'default a', 'd': 'test d'} def __init__(self, *args, **kwargs): pass # but can i do something with super? this fails but is the # approximate idea of what I want... # self.DEFAULTS.update(super(self.__class__, self).DEFAULTS) class B(A): DEFAULTS = {'b': 'default b'} class C(A): DEFAULTS = {'a': 'a overridden in C'} class D(C): DEFAULTS = {'d': 'd overridden in D'} def test(): a = A() b = B() c = C() d = D() print a.DEFAULTS print b.DEFAULTS print c.DEFAULTS print d.DEFAULTS assert (a.DEFAULTS == {'a': 'default a', 'd': 'test d'}) assert (b.DEFAULTS == {'a': 'default a', 'b': 'default b', 'd': 'test d'}) assert (c.DEFAULTS == {'a': 'overridden in c', 'd': 'test d'}) assert (d.DEFAULTS == {'a': 'overridden in c', 'd': 'd overridden in D'}) test() 

Of course, right now this leads to the following conclusion:

 {'a': 'default a', 'd': 'test d'} {'b': 'default b'} {'a': 'a overridden in C'} {'d': 'd overridden in D'} Traceback (most recent call last): File "experimental/users/edw/python/class_magic.py", line 36, in <module> test() File "experimental/users/edw/python/class_magic.py", line 32, in test assert (b.DEFAULTS == {'a': 'default a', 'b': 'default b', 'd': 'test d'}) AssertionError 
+4
source share
3 answers

Does this fit your needs?

An example with methods:

 class A(object): _DEFAULTS = {'a': 'a value'} @classmethod def get_defaults(cls): return cls._DEFAULTS class B(A): _B_DEFAULTS = {'b': 'b value'} @classmethod def get_defaults(cls): defaults = super(B, cls).get_defaults() defaults.update(cls._B_DEFAULTS) return defaults 

Metaclass Example:

 # Some data-structure (it should be refactored): _DEFAULTS = {'A': {'a': 'a_value'}, 'B': {'b': 'b_value'}} class DefaultsInitializer(type): def __call__(self, *args, **kwargs): obj = type.__call__(self) obj.defaults = _DEFAULTS[obj.__class__.__name__] for klass in obj.__class__.__bases__: if klass.__name__ in _DEFAULTS: obj.defaults.update(_DEFAULTS[klass.__name__]) return obj 

And some classes:

 >>> class A(object): ... __metaclass__ = DefaultsInitializer ... >>> a = A() >>> a.defaults {'a': 'a_value'} >>> class B(A): ... pass ... >>> b = B() >>> b.defaults {'a': 'a_value', 'b': 'b_value'} 
+1
source

Thanks sobolevn for an example metaclass. I built this to move the default values ​​inside the child classes, and not just one external data structure, such as the example in the original question. Performance can be a problem if the copied dictionary increases, but since it is a code like a config that would be unexpected in practice.

 class DefaultsMetaBase(type): """Automatically merges DEFAULTS from all parent classes.""" def __call__(self, *args, **kwargs): obj = type.__call__(self) for klass in obj.__class__.__mro__: if klass == obj.__class__ or klass == Base or not issubclass(klass, Base): continue if hasattr(klass, 'DEFAULTS'): d = klass.DEFAULTS.copy() d.update(obj.DEFAULTS) obj.DEFAULTS = d return obj class Base(object): __metaclass__ = DefaultsMetaBase class A(Base): DEFAULTS = {'a': 'default a', 'd': 'test d'} class B(A): DEFAULTS = {'b': 'default b'} class C(A): DEFAULTS = {'a': 'a overridden in C'} some_setting = ['a', 'b', 'c', 'd'] class D(C): DEFAULTS = {'d': 'd overridden in D'} some_setting = ['q', 'r', 's'] another_setting = 'moar roar' class F(D): another_setting = 'less roar' def test(): a = A() b = B() c = C() d = D() f = F() assert (a.DEFAULTS == {'a': 'default a', 'd': 'test d'}) assert (b.DEFAULTS == {'a': 'default a', 'b': 'default b', 'd': 'test d'}) assert (c.DEFAULTS == {'a': 'a overridden in C', 'd': 'test d'}) assert (d.DEFAULTS == {'a': 'a overridden in C', 'd': 'd overridden in D'}) assert (f.DEFAULTS == {'a': 'a overridden in C', 'd': 'd overridden in D'}) print 'pass!' test() 
+1
source

It seems to me that you want something like what usually happens with class attributes - children inherit parent attributes and can optionally hide parent attributes with their values.

eg.

 class A(object): a = 'a' class B(A): b = 'b' class C(A): a = 'c' assert Aa == 'a'and A().a == 'a' assert Ba == 'a' # B inherits a assert Ca == 'c' # C overrides a 

It also allows instances to override default values.

 a = A() aa = 'not a' assert Aa == 'a' and A().a == 'a' assert aa == 'not a' 

However, you may not want your default values ​​to be available this way. Do not worry. Define a default class for the parent and ask them to define child defaults. eg.

 class A(object): class defaults(object): a = 'a' class B(A): class defaults(A.defaults): b = 'b' class C(A): class defaults(A.defaults): a = 'c' assert A.defaults.a == 'a' and A().defaults.a == 'a' assert B.defaults.a == 'a' # B inherits a assert C.defaults.a == 'c' # C overrides a 

Or, really, you really want the default values ​​to be a dictate-like object, and then just define an object of type dict that overrides __getitem__ and various other methods you can take care of.

 def defaults(**kwargs): """decorator to add defaults to a class""" def default_setter(cls): if not hasattr(cls, "defaults"): cls.defaults = DefaultGetter() for key, value in kwargs: setattr(cls, "_default_"+key, value) return cls return default_setter class DefaultGetter(object): """dict-like object that retrieves the defaults""" def __getitem__(self, name): try: return getattr(self.owner, "_default_" + name) except AttributeError: raise KeyError(name) from None @defaults(a='a') class A(object): pass assert A.defaults['a'] == 'a' assert A._default_a == 'a' # actual storage location of a A.defaults['b'] # raises KeyError 
0
source

All Articles