I'm trying to make the following code work. I'd like to create a class attribute which is a dictionary of default values that automatically updates from children to parents through the class hierarchy. Ideally I'd like to do it with magic in the parent object so that child objects can simply override values as needed. I'd also be open to suggestions for how to redesign this if there's 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 results in the following output:
{'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
Thanks to sobolevn for the metaclass example. I built on that to move the defaults inside the child classes rather than a single external data structure like the example in the original question. Performance might be an issue if the dictionary being copied grows large but since this is code as 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()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With