Johan - 4 years ago 130
Python Question

# Calculate the delta in numeric values between two class instances

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})

What I want is to calculate the difference between two instances of the class. I've come up with the following approach, but not sure if this method has any drawbacks or maybe there's a better way?

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()

Alternatively I can add a decorator and don't use the Diff class:

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

Any feedback will be appreciated.

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
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download