Python 通过类层次结构合并dict类属性

Python 通过类层次结构合并dict类属性,python,class,python-2.7,inheritance,dictionary,Python,Class,Python 2.7,Inheritance,Dictionary,我正在努力使以下代码正常工作。我想创建一个class属性,它是默认值的字典,通过类层次结构自动从子级更新到父级。理想情况下,我希望在父对象中使用magic,这样子对象就可以根据需要简单地覆盖值。如果有关于这类事情的习惯用法,我也愿意接受如何重新设计的建议 class A(object): DEFAULTS = {'a': 'default a', 'd': 'test d'} def __init__(self, *args, **kwargs): pass # but

我正在努力使以下代码正常工作。我想创建一个class属性,它是默认值的字典,通过类层次结构自动从子级更新到父级。理想情况下,我希望在父对象中使用magic,这样子对象就可以根据需要简单地覆盖值。如果有关于这类事情的习惯用法,我也愿意接受如何重新设计的建议

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
{'a':'defaulta','d':'testd'}
{'b':'default b'}
{'a':'a在C'中被重写}
{'d':'d在d'中被重写}
回溯(最近一次呼叫最后一次):
文件“experimental/users/edw/python/class_magic.py”,第36行,在
测试()
文件“experimental/users/edw/python/class_magic.py”,第32行,在测试中
断言(b.DEFAULTS=={'a':'defaulta','b':'defaultb','d':'testd'})
断言错误

这是否适合您的需要

方法示例:

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 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()

我听起来像是希望类似于类属性通常发生的情况——子级继承父级属性,如果需要,可以使用自己的值隐藏父级属性

例如

它还允许实例覆盖默认值

a = A()
a.a = 'not a'
assert A.a == 'a' and A().a == 'a'
assert a.a == 'not a'
但是,您可能不希望您的默认设置像这样可访问。别担心。在父级上定义默认值类,并让子级定义子级默认值。例如

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
或者,如果您真的希望默认为类似dict的对象,那么只需定义一个类似dict的对象,该对象覆盖
\uuu getitem\uuuu
以及您可能关心的各种其他方法

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

除了使用元类之外,你不能用类属性来实现这一点,而元类的使用可能是过分的。你可以让它们成为实例属性,甚至属性。如果不显式地调用dict.update,你就不可能做你想做的事情。我只限于使用类属性,除非我去重写一堆相关的代码。我们目前的解决方案只是手动重新设置子类中的值,这没问题,但相当于复制/粘贴,我不喜欢。我对一个元类示例感兴趣,元类比我用过的更神奇。@padraic cunningham在上面的例子中,我可以在类A中调用update,但我确实试图避免在其子类中调用它。谢谢你,但不是。我特别尝试在子类中避免像get_defaults这样的方法。我认为元类版本可以被改装成原始版本若要使其生效,将在今天下午进行尝试。谢谢
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