我试图使以下代码工作。我想创建一个类属性,它是一个默认值的字典,通过类层次结构自动从子节点更新到父节点。理想情况下,我希望在父对象中使用魔法,以便子对象可以根据需要简单地覆盖值。我也愿意听取关于如何重新设计这个的建议,如果有一个关于这种事情的习语。
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()
当然,现在这会导致以下输出:
{'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
感谢sobolevn提供的元类示例。我在此基础上将默认值移动到子类中,而不是像原始问题中的示例那样移动到单个外部数据结构中。如果正在复制的字典变大,性能可能会出现问题,但由于这是配置代码,因此在实践中可能会出现意想不到的情况。
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()
这符合您的需求吗?
方法示例:
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
元类的例子:
# 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
和一些类:
>>> 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'}
听起来你想要的东西就像类属性通常发生的那样——子属性继承父属性,如果需要,可以用自己的值隐藏父属性。
。
class A(object):
a = 'a'
class B(A):
b = 'b'
class C(A):
a = 'c'
assert A.a == 'a'and A().a == 'a'
assert B.a == 'a' # B inherits a
assert C.a == 'c' # C overrides a
它还允许实例覆盖默认值
a = A()
a.a = 'not a'
assert A.a == 'a' and A().a == 'a'
assert a.a == 'not a'
然而,也许你不希望你的默认值是这样可访问的。不用担心。在父类上定义defaults类,并让子类定义子类defaults。如:
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
或者,如果你真的,真的,想要默认值是一个类似字典的对象,那么就定义一个类似字典的对象,覆盖__getitem__
和其他各种你可能关心的方法。
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