I have a Python class that performs a bunch of calculations. The class supports various calculations, each of which might or might not actually get called. Here is an example:
class MyCalc(object):
def __init__(user, query_date, award):
self.user = user
self.query_date = query_date
self.award = award
def balance(self): # this can be subtracted
return self.award.balance
def value(self): # this can be subtracted
if self.user.award_date > self.query_date:
return self.award.value * self.user.multiplier
return 0
def has_multiple_awards(self): # this can not be subtracted
return self.user.awards > 2
def as_pandas_series(self):
return pd.Series({'balance': self.balance(),
'value': self.value(),
'query_date': self.query_date,
'award': self.award,
'user': self.user})
class Diff(object):
def __init__(self, a, b):
self.a = a
self.b = b
def __getattr__(self, attr):
getter = operator.attrgetter(attr)
closing = getter(self.a)()
opening = getter(self.b)()
return closing - opening
a = MyCalc()
b = MyCalc()
diff = Diff(a, b)
print(diff.calc_x) # calculate a.calc_x() - b.calc_x()
def differance(func):
def func_wrapper(self):
return func(self) - func(self.b)
return func_wrapper
class MyCalc(object):
@difference
def calc_x(self):
return some_calc
@difference
def calc_y(self):
return some_calc
Your Diff
class looks fine to me, but I'm still undecided whether this is Pythonic or not. ;) I don't see any major drawbacks, but it can be made more efficient.
Here's an alternative implementation of the Diff
class. It's a little more efficient since it doesn't have to do a lookup and two calls of operator.attrgetter
on each __getattr__
call. Instead, it caches the attribute accessing functions using functools.partial
and the built-in getattr
function.
I've also implemented a simple MyCalc
class for testing purposes.
from functools import partial
class MyCalc(object):
def __init__(self, u, v):
self.u = u
self.v = v
def calc_x(self):
return self.u + self.v
def calc_y(self):
return self.u * self.v
class Diff(object):
def __init__(self, a, b):
self.geta = partial(getattr, a)
self.getb = partial(getattr, b)
def __getattr__(self, attr):
closing = self.geta(attr)()
opening = self.getb(attr)()
return closing - opening
a = MyCalc(10, 20)
b = MyCalc(2, 3)
diff = Diff(a, b)
print(diff.calc_x)
print(diff.calc_y)
a.u, a.v = 30, 40
b.u, b.v = 4, 7
print(diff.calc_x)
print(diff.calc_y)
output
25
194
59
1172