chinloon
chinloon

Reputation: 53

python: How to forward an instance's method call to its attribute

class Number(object):
    def __init__(self):
        super(Number, self).__init__()
        self.data = 10

    def __getattr__(self, name):
        def _missing(*args, **kwargs):
            method = getattr(self.data, name)
            return method(args[0])

        return _missing

a = Number()
b = Number()

print a.__add__(10)   # this is ok!
print a + 10          # TypeError: "unsupported operand type(s) for +: 'Number' and 'int'"
print a + b           # TypeError: "unsupported operand type(s) for +: 'Number' and 'Number'"

Question: What's the difference between "a.__add__(10)" and "a + 10", How can I hook the operator "+" ?

Upvotes: 5

Views: 4573

Answers (2)

Matthias
Matthias

Reputation: 4884

You can try this, but it's not beautiful:

import numbers

def redirect(name):
    def redirected(self, *args):
        assert len(args) <= 1
        if len(args) == 1 and isinstance(args[0], Number):
            return getattr(self._data, name)(args[0]._data)
        else:
            return getattr(self._data, name)(*args)
    return redirected

names = set(['__gt__', '__ge__'])
names.update(numbers.Real.__abstractmethods__)
methods = dict((name, redirect(name)) for name in names)
methods.update(
    {'__init__': lambda self, data: setattr(self, '_data', float(data))})
Number = type("Number", (), methods)

You can use it like this:

>>> a = Number(5)
>>> b = Number(7)
>>> a + 10
15.0
>>> a + b
12.0
>>> a > b
False
>>> a == Number(5.0)
True

Note that the type returned from the arithmetic operators is float and not Number (which may or may not be what you expected).

If you want to have integers, you can remove the float() call and change numbers.Real to numbers.Integral. Note, however, that the arithmetic operators will cease to work with float values.

Upvotes: 1

ThiefMaster
ThiefMaster

Reputation: 318508

Python will only use an actual __add__ method, not one that only "exists" due to __getattr__.

When adding __add__ = (10).__add__ it works fine.

So what you'll want to do is adding proxy methods:

def __add__(self, *args): return self.data.__add__(*args)
def __sub__(self, *args): return self.data.__sub__(*args)
# ...

Upvotes: 5

Related Questions