[英]How to access a superclass's class attributes in 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
,則應搜索超類的defaults
字典。
我正在嘗試使用遞歸類方法get_default
來實現它。 不幸的是,該程序陷入無限遞歸。 我對super()
理解顯然缺乏。 通過訪問__mro__
,我可以讓它正常工作,但我不確定這是一個合適的解決方案。
我覺得答案在本文的某個地方,但我還沒有找到它。 也許我需要求助於使用元類?
編輯:在我的應用程序中, __getattr__
self.base
首先檢查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
不是一個答案,而是一個觀察:
對我來說這看起來過於工作,在尋找使用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)
我認為麻煩是因為誤解了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人會認為這是一個非常奇怪的解決方案,如果你真的需要,你應該只使用它,也許是為了支持遺留代碼。
怎么樣:
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
更清楚你的“基礎”與“默認”案例。
>>> 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
這涵蓋了您陳述的用例。 你根本不需要魔法。 如果你的用例有所不同,請解釋它是什么,我們會告訴你如何做到這一點,沒有魔法。 ;)
在問題的第二次編輯中提出的解決方案仍然是唯一提供我的應用程序所需的一切的解決方案。 雖然unutbu的代碼可能更容易理解,但__mro__
解決方案提供了IMO的一些優點(參見注釋)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.