2017-02-07 11 views
1

私は一束の計算を実行するPythonクラスを持っています。このクラスはさまざまな計算をサポートしています。これらの計算のそれぞれは実際に呼び出されるかどうかは関係ありません。ここに例があります:2つのクラスインスタンス間の数値のデルタを計算します

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

私が欲しいのは、クラスの2つのインスタンスの違いを計算することです。私は次のアプローチを考え出しましたが、この方法に欠点があるかどうか、あるいはもっと良い方法があるかどうかはわかりません。

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のクラスを使用しないでください:

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 

すべてのフィードバックが理解されるであろう。

+0

興味深い概念です。最初のやり方をやや効率的にする方法はありますが、あなたが持っているものはいいと思います。私はかなりデコレータ版を取得していません。 MyCalcの2番目のインスタンスを '.b'属性として最初のインスタンスに格納する必要があるようですが、これはちょっと面倒です。 –

+0

はい、あなたは正しいです。デコレータのバージョンでは、MyCalcの2番目のインスタンスが必要です。ちょっと混乱しています。 – Johan

+1

*「クラスが計算を実行します」*と言うと、単純に値のリスト(またはNumPy配列)? NumPyでベクトル減算を行うだけです。また、よりPythonicようです。あなたは* "すべての計算がそれぞれ順番に呼び出されます*"、またはより "*クラスはさまざまな計算をサポートしています、それぞれが特定のインスタンスで実際に呼び出されるかもしれないかもしれません" *か?操作が何であるか、それらがお互いにどのように関連しているか、それらが連続して呼び出されるかどうかについて、少しの文脈を私たちに伝えれば助かります... – smci

答えて

1

あなたDiffクラスは、私には正常に見えるが、私はまだ、これはPython的であるかどうかは未定です。 ;)私は大きな欠点は見当たりませんが、より効率的にすることができます。

Diffクラスの代替実装を次に示します。これは、それぞれ__getattr__コールで検索を行う必要がなく、operator.attrgetterの2つのコールを行う必要がないので、もう少し効率的です。代わりに、functools.partialと組み込みのgetattr関数を使用して属性アクセス関数をキャッシュします。

また、私はテスト目的のために簡単なMyCalcクラスを実装しました。

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 
+0

すごく感謝、私は '部分的'については知らなかった – Johan

+0

@ジョハ:私の喜び! 'partial'にはいくつかの欠点がありますが、かなり効率的です。私は私の答えに 'partial'と' getattr'のためのdocsリンクを追加しました... –

1
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 
+0

質問に示されているように、Johanは単純な属性アクセスを使用することを望んでいます: 'diff.calc_x';あなたのコードの中の 'diff._diff(" calc_x ")コールは、比較するのが少し面倒です。 –

1

あなたがまたは呼び出さない場合がありますいくつかはすべて返却数値は、あなたのクラスは、約15の計算をサポートして言います。 クリーンで最もPython的なのは、1つのベクトル、つまりNumPy配列(またはPandas SeriesまたはDataFrame)を返すメソッドを1つ持つように思われます。クライアントコードは単にベクトル減算を行うことができます:ab_diff = a.calc() - b.calc()。あなたが記述したものに基づいて、np.arrayにホイールを再開発する必要はないようです。

これらの計算のうちのほんの一部が計算に費やされない場合や、計算コストが高い場合は、calc()calc_rare()にリファクタリングすることができます。または、あなたはcalc(..., compute_latlong=False, compute_expensive_stuff=False)にkwargsを渡すことができます。ベクトルの長さを一定に保つために、デフォルトで計算しない高価なもののデフォルト値として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() 
+1

私は詳細を追加して私の質問を更新しました。これは実際には素晴らしいアプローチです。値を計算するのは計算コストがかからないため、numpy配列を生成することは良い考えです。 – Johan

+0

うわー、数値的な(小さな負でない整数の)値の単なる束です。その場合、私はそれが多くの意味を持たないベクトル表現を返すために 'OOの濫用'に直面していると主張します。または、少なくとも、私は 'calc()'ではなく 'as_vector()'メソッドを呼び出します。実際には、2つのオブジェクト間に**カスタム距離メトリック**を計算しようとしていると思います。 [scipy.spatial.distance](https://docs.scipy.org/doc/scipy/reference/spatial.distance.html)、[sklearn.neighbors.DistanceMetric](http:// scikit-learn .org/stable/modules/generated/sklearn.neighbors.DistanceMetric.html)など。 – smci

+0

基本的に私が計算しようとしているのは次のとおりです:Calcクラスは現在、単一のquery_dateで動作しています。それは、他のものの中でユーザーの残高を計算します。私は別の日に別のcalcクラスを持っていて、日付間のバランスを変えたいと思っています。ユーザー名のようなものはコースから変わらない。 – Johan

関連する問題