Search code examples
pythonobjectmagic-methodsgetattrgetattribute

Is there any way to override the double-underscore (magic) methods of arbitrary objects in Python?


I want to write a wrapper class which takes a value and behaves just like it except for adding a 'reason' attribute. I had something like this in mind:

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

    def __getattribute__(self, name):
        print '__getattribute__ with %s called' % (name,)
        if name in ('__str__', '__repr__', 'reason', 'value'):
            return object.__getattribute__(self, name)
        value = object.__getattribute__(self, 'value')
        return object.__getattribute__(value, name)

    def __str__(self):
        return "ExplainedValue(%s, %s)" % (
            str(self.value),
            self.reason)
    __repr__ = __str__

However, the double-underscore functions don't seem to be captured with __getattribute__, for example:

>>> numbers = ExplainedValue([1, 2, 3, 4], "it worked")
>>> numbers[0]

Traceback (most recent call last):
  File "<pyshell#118>", line 1, in <module>
    numbers[0]
TypeError: 'ExplainedValue' object does not support indexing
>>> list(numbers)
__getattribute__ with __class__ called

Traceback (most recent call last):
  File "<pyshell#119>", line 1, in <module>
    list(numbers)
TypeError: 'ExplainedValue' object is not iterable

I would think the two above should end up doing this:

>>> numbers.value[0]
__getattribute__ with value called
1

>>> list(numbers.value)
__getattribute__ with value called
[1, 2, 3, 4]

Why is this not happening? How can I make it happen? (This might be a horrible idea to actually use in real code but I'm curious about the technical issue now.)


Solution

  • As millimoose says, an implicit __foo__ call never goes through __getattribute__. The only thing you can do is actually add the appropriate functions to your wrapper class.

    class Wrapper(object):
        def __init__(self, wrapped):
            self.wrapped = wrapped
    
        for dunder in ('__add__', '__sub__', '__len__', ...):
            locals()[dunder] = lambda self, __f=dunder, *args, **kwargs: getattr(self.wrapped, __f)(*args, **kwargs)
    
    obj = [1,2,3]
    w = Wrapper(obj)
    print len(w)
    

    Class bodies are executed code like any other block (well, except def); you can put loops and whatever else you want inside. They're only magical in that the entire local scope is passed to type() at the end of the block to create the class.

    This is, perhaps, the only case where assigning to locals() is even remotely useful.