Have a look at the following code:
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)
I have a hierarchy of classes each with its own dictionary containing some default values. If an instance of a class doesn't have a particular attribute, a default value for it should be returned instead. If no default value for the attribute is contained in the current class's defaults
dictionary, the superclass's defaults
dictionary should be searched.
I'm trying to implement this using the recursive class method get_default
. The program gets stuck in an infinite recursion, unfortunately. My understanding of super()
is obviously lacking. By accessing __mro__
, I can get it to work properly though, but I'm not sure this is a proper solution.
I have the feeling the answer is somewhere in this article, but I haven't been able to find it yet. Perhaps I need to resort to using a metaclass?
edit: In my application, __getattr__
first checks self.base
. If it is not None
, the attribute needs to be fetched from there. Only in the other case, a default value must be returned. I could probably override __getattribute__
. Would that be the better solution?
edit 2: Below is an extended example of the functionality that I'm looking for. It is currently implemented using __mro__
(unutbu's earlier suggestion, as opposed to my original recursive method). Unless someone can suggest a more elegant solution, I'm happy using this implementation. I hope this clears things up.
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
The output:
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
Not really an answer but an observation:
This looks overengineered to me, a common trap when looking for excuses to use python magic.
If you can be bothered to define a defaults
dict for a class why not just define the attributes instead? the effect is the same.
class A:
a = 1
class B(A):
b = 2
class C(B):
c = 3
c = C()
print('c.a =', c.a)
EDIT:
As for answering the question, I would probably use __getattribute__
in combination with my suggestion like this:
def __getattribute__(self, name):
try:
return object.__getattribute__(self.base, name)
except AttributeError:
return object.__getattribute__(self, name)
I think the trouble results from misunderstanding the purpose of super()
.
http://docs.python.org/library/functions.html#super
Essentially, wrapping your object (or class) in super() makes Python skip the most-recently-inherited class when doing attribute lookup. In your code, this results in class C being skipped when looking for get_default, but this doesn't really do anything, since C doesn't define a get_default anyway. Naturally, this results in an infinite loop.
The solution is to define this function in each class that derives from A. This can be done using a metaclass:
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)
results:
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)
I should note that most Python people will consider this a very bizarre solution, and you should only use this if you really need to, maybe to support legacy code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With