'super' object not calling __getattr__
Asked Answered
K

1

20

I have one object wrapped inside another. The "Wrapper" accesses the attributes from the "Wrapped" object by overriding __getattr__. This works well until I need to override an atribute on a sub class, and then access the attribute from the base class using super().

I can still access the attribute directly from __getattr__ but why does super() not work?

class Wrapped(object):
    def __init__(self, value):
        self.value = value

    def hello_world(self):
        print 'hello world', self.value

class Wrapper(object):
    def __init__(self, obj):
        self.wrapped_obj = obj

    def __getattr__(self, name):
        if name in self.__dict__:
            return getattr(self, name)
        else:
            return getattr(self.wrapped_obj, name)

class Subclass(Wrapper):
    def __init__(self, obj):
        super(Subclass, self).__init__(obj)

    def hello_world(self):
        # this works
        func = super(Subclass, self).__getattr__('hello_world')()
        # this doesn't
        super(Subclass, self).hello_world()

a = Wrapped(2)
b = Subclass(a)
b.hello_world()
Kittie answered 21/8, 2012 at 2:50 Comment(2)
I'm not sure if I'm reading the documentation correctly, but this seems to be a feature: Note that super() is implemented as part of the binding process for explicit dotted attribute lookups such as super().__getitem__(name). It does so by implementing its own __getattribute__() method for searching classes in a predictable order that supports cooperative multiple inheritance. Accordingly, super() is undefined for implicit lookups using statements or operators such as super()[name].Spikelet
On a side note, your __getattr__'s code inside if name in self.__dict__: will never be reached: __getattr__ is not called if a name is defined in the instance's __dict__. This is a potential source of hard to find bugs.Mcdade
W
16

According to this, super does not allow implicit calls of "hook" functions such as __getattr__. I'm not sure why it is implemented this way (there's probably a good reason and things are already confusing enough since the super object has custom __getattribute__ and __get__ methods as it is), but it seems like it's just the way things are.

Edit: This post appears to clear things up a little. It looks like the problem is the extra layer of indirection caused by __getattribute__ is ignored when calling functions implicitly. Doing foo.x is equivalent to

foo.__getattr__(x)

(Assuming no __getattribute__ method is defined and x is not in foo.__dict__) However, it is NOT equivalent to

foo.__getattribute__('__getattr__')(x)

Since super returns a proxy object, it has an extra layer of indirection which causes things to fail.

P.S. The self.__dict__ check in your __getattr__ function is completely unnecessary. __getattr__ is only called if the attribute doesn't already exist in your dict. (Use __getattribute__ if you want it to always be called, but then you have to be very careful, because even something simple like if name in self.__dict__ will cause infinite recursion.

Waaf answered 21/8, 2012 at 3:31 Comment(2)
This does look like a bug - at least it does not adhere to the Least Surprise Principle ;)Fulton
@Tony on the other hand, there is a strong case for limiting indirection of magic methods. Not only for performance, but because otherwise you'd get into an infinite loop in some cases (such as having to call __getattribute__ to find the __getattribute__ method!)Waaf

© 2022 - 2024 — McMap. All rights reserved.