计算两个 class 实例之间的数值增量

Calculate the delta in numeric values between two class instances

我有一个 Python class 可以执行大量计算。 class 支持各种计算,每个计算可能会或可能不会实际被调用。这是一个例子:

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 的两个实例之间的差异。我想出了以下方法,但不确定这种方法是否有任何缺点或者是否有更好的方法?

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

或者我可以添加装饰器而不使用 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

如有任何反馈,我们将不胜感激。

import operator

class MyCalc(object):
    def __init__(self, x=0, y=0, *args):
      self.x = x
      self.y = y

    def calc_x(self):
        return self.x * 2

    def calc_y(self):  # There's about 15 of these calculations
        return self.y / 2

class Diff(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def _diff(self, func, *args):
      getter = operator.attrgetter(func)
      closing = getter(self.a)()
      opening = getter(self.b)()
      return closing - opening


a = MyCalc(50)
b = MyCalc(100)

diff = Diff(a, b)

ret =  diff._diff("calc_x")
print ret


>>> -100

你的 Diff class 对我来说看起来不错,但我仍然不确定这是否是 Pythonic 的。 ;) 我没有看到任何重大缺点,但可以提高效率。

这是 Diff class 的另一种实现方式。它的效率更高一些,因为它不必在每次 __getattr__ 调用时执行查找和两次 operator.attrgetter 调用。相反,它使用 functools.partial and the built-in getattr 函数缓存属性访问函数。

我还实现了一个简单的 MyCalc class 用于测试目的。

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)

输出

25
194
59
1172

你说你的 class 支持大约 15 个计算,所有 returning 数值,其中一些可能会或可能不会被调用。 最干净和最 Pythonic 似乎有一个 calc() 方法 returning 向量,即 NumPy 数组(或 Pandas 系列或 DataFrame)。然后客户端代码可以简单地做向量减法:ab_diff = a.calc() - b.calc()。 np.array 似乎没有必要重新发明轮子,只是根据你所描述的。

如果其中一些计算 rarely-called and/or 的计算成本很高,那么您可以重构为 calc()calc_rare()。或者,您可以将 kwargs 传递给 calc(..., compute_latlong=False, compute_expensive_stuff=False)。您可以 return np.NaN 作为您默认不计算的昂贵内容的默认值,以保持向量长度不变。

import numpy as np
#import pandas as pd

class MyCalc(object):
    def __init__(self, ...): ...

   # (You can either have 15 calculation methods, or use properties.
   #  It depends on whether any of these quantities are interrelated
   #  or have shared dependencies, especially expensive ones.)
    def calc_q(self): ...
    def calc_r(self): ...
    def calc_s(self): ...
    ...
    def calc_y(self): ...
    def calc_z(self): ...

    # One main calc() method for the client. (You might hide the
    # other calc_* methods as _calc_*, or else in properties.)
    def calc(self):
        return np.array([ calc_q(), calc_r(), calc_s(),
            ... calc_y(), calc_z() ]) # Refactor this as you see fit

if __name__ == '__main__':
    # Client is as simple as this
    a = MyCalc(...)
    b = MyCalc(...)
    ab_diff = a.calc() - b.calc()