如何在Python中访问超类的类属性?

时间:2011-01-05 20:41:49

标签: python super superclass

查看以下代码:

class A(object):
    defaults = {'a': 1}

    def __getattr__(self, name):
        print('A.__getattr__')
        return self.get_default(name)

    @classmethod
    def get_default(cls, name):
        # some debug output
        print('A.get_default({}) - {}'.format(name, cls))
        try:
            print(super(cls, cls).defaults) # as expected
        except AttributeError: #except for the base object class, of course
            pass

        # the actual function body
        try:
            return cls.defaults[name]
        except KeyError:
            return super(cls, cls).get_default(name) # infinite recursion
            #return cls.__mro__[1].get_default(name) # this works, though

class B(A):
    defaults = {'b': 2}

class C(B):
    defaults = {'c': 3}


c = C()
print('c.a =', c.a)

我有一个类的层次结构,每个类都有自己的包含一些默认值的字典。如果类的实例没有特定属性,则应返回其默认值。如果当前类的defaults字典中没有包含该属性的默认值,则应搜索超类的defaults字典。

我正在尝试使用递归类方法get_default来实现它。不幸的是,该程序陷入无限递归。我对super()的理解显然缺乏。通过访问__mro__,我可以让它正常工作,但我不确定这是一个正确的解决方案。

我觉得答案在this article的某个地方,但我还没有找到答案。也许我需要求助于使用元类?

编辑:在我的申请中,__getattr__首先检查self.base。如果它不是None,则需要从那里获取属性。仅在其他情况下,必须返回默认值。我可以覆盖__getattribute__。这会是更好的解决方案吗?

编辑2:以下是我正在寻找的功能的扩展示例。它目前使用__mro__(unutbu之前的建议,而不是我原来的递归方法)实现。除非有人能提出更优雅的解决方案,否则我很高兴使用此实现。我希望这能解决问题。

class A(object):
    defaults = {'a': 1}

    def __init__(self, name, base=None):
        self.name = name
        self.base = base

    def __repr__(self):
        return self.name

    def __getattr__(self, name):
        print(" '{}' attribute not present in '{}'".format(name, self))
        if self.base is not None:
            print("  getting '{}' from base ({})".format(name, self.base))
            return getattr(self.base, name)
        else:
            print("  base = None; returning default value")
            return self.get_default(name)

    def get_default(self, name):
        for cls in self.__class__.__mro__:
            try:
                return cls.defaults[name]
            except KeyError:
                pass
        raise KeyError

class B(A):
    defaults = {'b': 2}

class C(B):
    defaults = {'c': 3}


c1 = C('c1')
c1.b = 55

print('c1.a = ...'); print('   ...', c1.a) # 1
print(); print('c1.b = ...'); print('   ...', c1.b) # 55
print(); print('c1.c = ...'); print('   ...', c1.c) # 3

c2 = C('c2', base=c1)
c2.c = 99

print(); print('c2.a = ...'); print('   ...', c2.a) # 1
print(); print('c2.b = ...'); print('   ...', c2.b) # 55
print(); print('c2.c = ...'); print('   ...', c2.c) # 99

输出:

c1.a = ...
 'a' attribute not present in 'c1'
  base = None; returning default value
   ... 1

c1.b = ...
   ... 55

c1.c = ...
 'c' attribute not present in 'c1'
  base = None; returning default value
   ... 3

c2.a = ...
 'a' attribute not present in 'c2'
  getting 'a' from base (c1)
 'a' attribute not present in 'c1'
  base = None; returning default value
   ... 1

c2.b = ...
 'b' attribute not present in 'c2'
  getting 'b' from base (c1)
   ... 55

c2.c = ...
   ... 99

6 个答案:

答案 0 :(得分:8)

不是一个答案,而是观察:

对于我来说,这看起来过于工作,在寻找使用python魔法的借口时,这是一个常见的陷阱。

如果您为某个类定义defaults dict而烦恼,为什么不直接定义属性呢?效果是一样的。

class A:
    a = 1

class B(A):
    b = 2

class C(B):
    c = 3


c = C()
print('c.a =', c.a)

修改

至于回答这个问题,我可能会将__getattribute__与我的建议结合使用:

def __getattribute__(self, name):
    try:
        return object.__getattribute__(self.base, name)
    except AttributeError:
        return object.__getattribute__(self, name)

答案 1 :(得分:2)

我认为麻烦是因为误解了super()的目的。

http://docs.python.org/library/functions.html#super

本质上,将对象(或类)包装在super()中会使Python在执行属性查找时跳过最近继承的类。在你的代码中,这会导致在查找get_default时跳过类C,但这实际上没有做任何事情,因为C无论如何都没有定义get_default。当然,这会导致无限循环。

解决方案是在每个派生自A的类中定义此函数。 这可以使用元类来完成:

class DefaultsClass(type):
    def __init__(cls, name, bases, dct):

        def get_default(self, name):
            # some debug output
            print('A.get_default(%s) - %s' % (name, cls))
            try:
                print(cls.defaults) # as expected
            except AttributeError: #except for the base object class, of course
                pass

            # the actual function body
            try:
                return cls.defaults[name]
            except KeyError:
                return super(cls, self).get_default(name) # cooperative superclass

        cls.get_default = get_default
        return super(DefaultsClass, cls).__init__(name, bases, dct)

class A(object):
    defaults = {'a': 1}
    __metaclass__ = DefaultsClass

    def __getattr__(self, name):
        return self.get_default(name)



class B(A):
    defaults = {'b': 2}

class C(B):
    defaults = {'c': 3}


c = C()
print('c.a =', c.a)
print('c.b =', c.b)
print('c.c =', c.c)

结果:

A.get_default(c) - <class '__main__.C'>
{'c': 3}
('c.c =', 3)
A.get_default(b) - <class '__main__.C'>
{'c': 3}
A.get_default(b) - <class '__main__.B'>
{'b': 2}
('c.b =', 2)
A.get_default(a) - <class '__main__.C'>
{'c': 3}
A.get_default(a) - <class '__main__.B'>
{'b': 2}
A.get_default(a) - <class '__main__.A'>
{'a': 1}
('c.a =', 1)

我应该注意到,大多数Python人会认为这是一个非常奇怪的解决方案,如果真的需要,你应该只使用它,可能支持遗留代码。

答案 2 :(得分:1)

怎么样:

class A(object):
    def __init__(self,base=None):
        self.a=1
        if base is not None:
            self.set_base(base)
        super(A,self).__init__() 
    def set_base(self,base):
        for key in ('a b c'.split()):
            setattr(self,key,getattr(base,key))
class B(A): 
    def __init__(self,base=None):
        self.b=2
        super(B,self).__init__(base)        
class C(B): 
    def __init__(self,base=None):
        self.c=3
        super(C,self).__init__(base)

c1=C()
c1.b=55
print(c1.a)
print(c1.b)
print(c1.c)
# 1
# 55
# 3

c2=C(c1)
c2.c=99
print(c2.a)
print(c2.b)
print(c2.c)
# 1
# 55
# 99

c1.set_base(c2)
print(c1.a)
print(c1.b)
print(c1.c)
# 1
# 55
# 99

答案 3 :(得分:1)

更清楚你的“基础”与“默认”案例。

>>> class A(object):
...     a = 1
... 
>>> class B(A):
...     b = 2
... 
>>> class C(B):
...     c = 3
... 
>>> a = A()
>>> b = B()
>>> c = C()
>>> 
>>> b.b = 23
>>> b.a
1
>>> b.b
23
>>> c.a
1
>>> c.b
2
>>> c.c
3
>>> c.c = 45
>>> c.c
45

这涵盖了您陈述的用例。你根本不需要魔法。如果你的用例有所不同,请解释它是什么,我们会告诉你如何做到这一点,没有魔法。 ;)

答案 4 :(得分:0)

您应该在这里使用name mangling

defaults重命名为__defaults

这为每个类提供了一个明确的属性,因此它们不会相互混淆

答案 5 :(得分:0)

在问题的第二次编辑中提出的解决方案仍然是唯一提供我的应用程序所需的一切的解决方案。虽然unutbu的代码可能更容易理解,但__mro__解决方案提供了IMO的一些优势(见评论)。