如何在Python中访问父类的类属性?
看看下面的代码:
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 个回答
我觉得问题出在对 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 开发者会觉得这是个非常奇怪的解决方案,只有在你真的需要的时候,比如为了支持旧代码,才应该使用它。
这其实不是一个答案,而是我的观察:
我觉得这个设计有点过于复杂了,这种情况很常见,尤其是在想要找借口使用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)
问题的第二次编辑中提出的解决方案仍然是唯一一个能满足我应用需求的方案。虽然unutbu的代码可能更容易理解,但我认为__mro__
的解决方案有一些优势(可以查看评论)。