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

6 投票
6 回答
15491 浏览
提问于 2025-04-16 09:28

看看下面的代码:

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__,我能让它正常工作,但我不确定这是否是个合适的解决方案。

我觉得答案可能在 这篇文章里,但我还没找到。也许我需要使用元类?

补充:在我的应用中,__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 个回答

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 开发者会觉得这是个非常奇怪的解决方案,只有在你真的需要的时候,比如为了支持旧代码,才应该使用它。

8

这其实不是一个答案,而是我的观察:

我觉得这个设计有点过于复杂了,这种情况很常见,尤其是在想要找借口使用Python的一些高级特性时。

如果你已经愿意为一个类定义一个defaults字典,那为什么不直接定义属性呢?效果是一样的。

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

问题的第二次编辑中提出的解决方案仍然是唯一一个能满足我应用需求的方案。虽然unutbu的代码可能更容易理解,但我认为__mro__的解决方案有一些优势(可以查看评论)。

撰写回答