Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python: generalizing delegating a method

I have a class like this containing one or more numeric elements.

class Foo:
    # ... other methods ...
    def _update(self, f):
        # ... returns a new Foo() object based on transforming
        #     one or more data members with a function f()
    def __add__(self, k):
        return self._update(lambda x: x.__add__(k))
    def __radd__(self, k):
        return self._update(lambda x: x.__radd__(k))
    def __sub__(self, k):
        return self._update(lambda x: x.__sub__(k))
    def __rsub__(self, k):
        return self._update(lambda x: x.__rsub__(k))
    def __mul__(self, k):
        return self._update(lambda x: x.__mul__(k))
    def __rmul__(self, k):
        return self._update(lambda x: x.__rmul__(k))
    def __div__(self, k):
        return self._update(lambda x: x.__div__(k))
    def __rdiv__(self, k):
        return self._update(lambda x: x.__rdiv__(k))
    # I want to add other numeric methods also

Is there any way to generalize this for all the numeric methods, without having to do this for each and every one of them?

I just want to delegate for any method in the list of numeric methods.

like image 714
Jason S Avatar asked Feb 27 '26 20:02

Jason S


2 Answers

You want to use the operator module here, together with a (short) list of binary numeric operator names, without the underscores for compactness:

import operator 

numeric_ops = 'add div floordiv mod mul pow sub truediv'.split()

def delegated_arithmetic(handler):
    def add_op_method(op, cls):
        op_func = getattr(operator, op)
        def delegated_op(self, k):
            getattr(self, handler)(lambda x: op_func(x, k))
        setattr(cls, '__{}__'.format(op), delegated_op)

    def add_reflected_op_method(op, cls):
        op_func = getattr(operator, op)
        def delegated_op(self, k):
            getattr(self, handler)(lambda x: op_func(k, x))
        setattr(cls, '__r{}__'.format(op), delegated_op)

    def decorator(cls):
        for op in numeric_ops:
            add_op_method(op, cls)
            add_reflected_op_method(op, cls) # reverted operation
            add_op_method('i' + op, cls)     # in-place operation
        return cls

    return decorator

Now just decorate your class:

@delegated_arithmetic('_update')
class Foo:
    # ... other methods ...
    def _update(self, f):
        # ... returns a new Foo() object based on transforming
        #     one or more data members with a function f()

The decorator takes the name you wanted to delegate the call to to make this a little more generic.

Demo:

>>> @delegated_arithmetic('_update')
... class Foo(object):
...     def _update(self, f):
...         print 'Update called with {}'.format(f)
...         print f(10)
... 
>>> foo = Foo()
>>> foo + 10
Update called with <function <lambda> at 0x107086410>
20
>>> foo - 10
Update called with <function <lambda> at 0x107086410>
0
>>> 10 + foo
Update called with <function <lambda> at 0x107086410>
20
>>> 10 - foo
Update called with <function <lambda> at 0x107086410>
0
like image 171
Martijn Pieters Avatar answered Mar 01 '26 10:03

Martijn Pieters


Use a class decorator:

def add_numerics(klass):
    for numeric_fn in ['add','radd','sub','rsub','mul','rmul','div','rdiv']:
        dunder_fn = '__{}__'.format(numeric_fn)
        setattr(klass, dunder_fn, lambda self, k: self._update(lambda x: getattr(x, dunder_fn)(k)))
    return klass

@add_numerics
class Foo:
    def _update(self, f):
        # ...
        return Foo()
like image 38
Jon Gauthier Avatar answered Mar 01 '26 09:03

Jon Gauthier



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!