Reputation: 189836
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.
Upvotes: 3
Views: 567
Reputation: 25592
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()
Upvotes: 2
Reputation: 1124258
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
Upvotes: 3
Reputation: 532093
This might be too general, as it will dispatch any attribute not explicitly declared.
class Foo:
def _update(self, f):
...
def __getattr__(self, attr):
def __(self, k):
return self._update( lambda x: x.__getattr__(attr)(k) )
return __
You can make it a little more selective:
def __getattr__(self, attr):
if attr in ['__add__', '__radd__', ... ]:
def __(self, k):
return self._update( lambda x: x.__getattr__(attr)(k) )
return __
else:
raise AttributeError("Unknown attribute: {0}".format(attr))
Upvotes: 0