Question

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
Was it helpful?

Solution 6

The solution proposed in the second edit of the question is still the only one that provides everything my application requires. While unutbu's code might be simpler to understand, the __mro__ solution provides some advantages IMO (see comments).

OTHER TIPS

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.

To be more clear about your "base" vs "default" case.

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

This covers your stated use case. You need no magick at all. If your usecase is somewhat different, explain what it is, and we'll tell you how to do that with no magick. ;)

How about:

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

You should use name mangling here.

Rename defaults to __defaults

That gives each class an unambiguous attribute so they are not confused with each other

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top