PHPの流動的なインターフェースに関する最近の投稿に触発されて、私はすぐにこれをpythonでより簡単に、より美しく実装する方法を疑問に思いました(pythonは常によりシンプルでより美しいです)。 考えを受け取る順にいくつかの方法を提供します。
最初の方法-おでこに
演算子のチェーンを構築するには、クラスのインスタンスを返す関数が必要です。 これは手動で設定できます。
def add(self,x): self.val += x return self
明らかに、このアプローチは完全に機能しますが、もう少し探しています。
2番目の方法-デコレータ
これが頭に浮かんだ最初のアイデアです。 クラス内のメソッドのデコレーターを定義します。 インスタンスが最初の引数として渡されることを非常に嬉しく思います。
def chained(fn): def new(*args,**kwargs): fn(*args,**kwargs) return args[0] return new class UsefulClass1(): def __init__(self,val): self.val = val @chained def add(self,val): self.val += val @chained def mul(self,val): self.val *= val
デコレータを使用して、チェーンで使用する必要がある関数をマークするだけです。 戻り値は無視され、代わりにクラスインスタンスが渡されます。
>>> print UsefulClass1(10).add(5).mul(10).add(1).val 151
メソッドは明らかに読みやすいです-チェーンで使用できる関数をすぐに確認できます。 ただし、通常、アーキテクチャのアプローチは、一度に1つのマークを付けるのに関心のないほとんどのクラスメソッドに拡張されます。
第三の方法-自動
関数を呼び出すとき、戻り値を確認できます。 存在しない場合は、オブジェクト自体を転送します。 これは__getattribute__を介して行い、クラスのメソッドとフィールドへのアクセスをインターセプトします。 まず、同様の動作を持つクラスを定義するだけで、そこからすべての作業クラスを継承します。
from types import MethodType class Chain(object): def __getattribute__(self,item): fn = object.__getattribute__(self,item) if fn and type(fn)==MethodType: def chained(*args,**kwargs): ans = fn(*args,**kwargs) return ans if ans!=None else self return chained return fn class UsefulClass2(Chain): val = 1 def add(self,val): self.val += val def mul(self,val): self.val *= val def third(self): return 386
メソッドが値を返す場合、値が渡されます。 そうでない場合、クラスインスタンス自体が代わりに移動します。
>>> print UsefulClass2().add(15).mul(16).add(-5).val 251 >>> print UsefulClass2().third() 386
これで、親クラスの1つとして連鎖クラスを指定する以外の方法で、作業クラスを変更する必要がなくなりました。 明らかな欠点は、__ getattribute__を自分の目的に使用できないことです。
4番目の方法-Im So Meta ...
メタクラスを使用して、作業クラスの必要なラッパーを整理できます。 後者を初期化するとき、その場で__getattribute__をラップします(そして、その不在も私たちを悩ませません)。
from types import MethodType class MetaChain(type): def __new__(cls,name,bases,dict): old = dict.get('__getattribute__',object.__getattribute__) def new_getattribute(inst,val): attr = old(inst,val) if attr==None: return inst if attr and type(attr)==MethodType: def new(*args,**kwargs): ans = attr(*args,**kwargs) return ans if ans!=None else inst return new return attr dict['__getattribute__'] = new_getattribute return type.__new__(cls,name,bases,dict) class UsefulClass3(): __metaclass__ = MetaChain def __getattribute__(self,item): if item=="dp": return 493 return object.__getattribute__(self,item) val = 1 def add(self,val): self.val += val def mul(self,val): self.val *= val
前のものと実質的に違いはありません-メタクラスを使用して__getattribute__の作成のみを制御します。 作業クラスをラップするには、__ metaclass__を指定するだけです。
>>> print UsefulClass3().dp 493 >>> print UsefulClass3().add(4).mul(5).add(1).mul(25).add(-1).val 649
ご覧のとおり、元々労働者階級で利用できた__getattribute__が機能します。 作業クラスから継承する場合、動作は保持されます-__getattribute__も継承されます。 ネイティブの__getattribute__が何も返さない場合(AttributeErrorも含む)、オブジェクト自体も返します。
結論の代わりに
流体インターフェースのユビキタスな使用は疑わしいですが、そのような構造が適切である場合がまだあります。 たとえば、画像処理または多くの操作が順次実行されるエンティティ。
PS私の意見では、最後のオプションには明らかな欠陥は含まれていません。 誰もが最良の選択肢を提供できる場合-私は聞いてうれしく思います、また私の欠点の兆候。
PPS労働者のリクエストにより、
言及された記事と
ウィキペディアの説明へのリンク
更新する5番目の方法-ローストと囲炉裏
同志
davinchiは、すべての呼び出しのラップが少なくとも奇妙であることを正しく指摘しました。 さらに、オブジェクトのフィールドにアクセスするたびにチェックを実行します。
次に、すべてのメソッドを一度に処理しますが、メソッドの変更と作成をチェックして、それらをラップすることもできます。
class NewMetaChain(type): def __new__(cls,name,bases,dict): old = dict.get('__setattr__',object.__setattr__) def wrap(fn,inst=None): def new(*args,**kwargs): ans = fn(*args,**kwargs) return ans if ans!=None else inst or args[0] return new special = dir(cls) for item, fn in dict.items(): if item not in special and isinstance(fn,FunctionType): dict[item] = wrap(fn) def new_setattr(inst,item,val): if isinstance(val,FunctionType): val = wrap(val,inst) return old(inst,item,val) dict['__setattr__'] = new_setattr return type.__new__(cls,name,bases,dict) class UsefulClass4(): __metaclass__ = NewMetaChain def __setattr__(self,item,val): if val == 172: val = "giza" object.__setattr__(self, item, val) val = 1 def add(self,val): self.val += val def mul(self,val): self.val *= val def nul(self): pass
呼び出しごとにメソッドをラップしないことに加え(速度が30%向上しました)、オブジェクトのフィールドの読み取りごとではなく、レコードごとに(必要な頻度は低くなります)必要なチェックも実行します。 エントリがない場合、decoratorメソッドと同じ速さで動作します。