Johan - 2 years ago 125
Python Question

Calculate the delta in numeric properties 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