2010-11-30 10 views
18

ユーザーの機能を呼び出すためのインターフェイスを提供することで、自分のモジュールのユーザーがその機能を拡張する機能を提供したいと考えています。たとえば、クラスのインスタンスが作成されたときに通知を受け、インスタンスを使用する前にそのインスタンスを変更する機会をユーザーに与えたいとします。Pythonでフックやコールバックを実装するのに好ましい方法は何ですか?

私はそれがインスタンス化を行うモジュールレベルのファクトリ関数を宣言することです実装しました方法:私はのmymoduleにクラスのインスタンスを必要とするとき

# in mymodule.py 
def factory(cls, *args, **kwargs): 
    return cls(*args, **kwargs) 

はその後、私はむしろcls(arg1, arg2)よりfactory(cls, arg1, arg2)を行います。プログラマが別のモジュールにこのような関数を記述し、それを拡張するために

:上記のコールバックの

def myFactory(cls, *args, **kwargs): 
    instance = myFactory.chain(cls, *args, **kwargs) 
    # do something with the instance here if desired 
    return instance 

インストールは次のようになります。

myFactory.chain, mymodule.factory = mymodule.factory, myFactory 

これは私には十分に簡単なようで、 Pythonプログラマーとして、代入で関数をコールするのではなく、コールバックを登録する関数があると期待していたのか、他のメソッドがあれば期待していたのでしょうか?私のソリューションは実用的で、慣用的で、あなたには明らかですか?

私はできるだけシンプルにしていきたいと考えています。私は、ほとんどのアプリケーションが実際には複数のユーザーコールバックをチェーン化する必要があるとは思わない(例えば、無制限の連鎖は上記のパターンで "無料"になる)。コールバックを削除したり、優先順位や順序を指定する必要があるかどうかは疑問です。 python-callbacksPyDispatcherのようなモジュールは、私には過剰な、特に後者のように思えますが、私のモジュールで作業しているプログラマーにとって魅力的な利点があるならば、私はそれらを公開しています。

+1

単純なサブクラスを使用してクラスを拡張すると何が問題になりますか?なぜこのすべての混乱のコールバック事業ですか? –

答えて

7

接続されたコールバック、さらにC#から借りたコンセプトについては、これを思いついた:

class delegate(object): 

    def __init__(self, func): 
     self.callbacks = [] 
     self.basefunc = func 

    def __iadd__(self, func): 
     if callable(func): 
      self.__isub__(func) 
      self.callbacks.append(func) 
     return self 

    def callback(self, func): 
     if callable(func): 
      self.__isub__(func) 
      self.callbacks.append(func) 
     return func 

    def __isub__(self, func): 
     try: 
      self.callbacks.remove(func) 
     except ValueError: 
      pass 
     return self 

    def __call__(self, *args, **kwargs): 
     result = self.basefunc(*args, **kwargs) 
     for func in self.callbacks: 
      newresult = func(result) 
      result = result if newresult is None else newresult 
     return result 

@delegateで関数をデコレートすると、他の関数をその関数に「添付」することができます。

@delegate 
def intfactory(num): 
    return int(num) 

機能+=とデリゲートに添加し(そして-=で除去する)ことができます。 funcname.callbackで飾ることで、コールバック関数を追加することもできます。

@intfactory.callback 
def notify(num): 
    print "notify:", num 

def increment(num): 
    return num+1 

intfactory += increment 
intfactory += lambda num: num * 2 

print intfactory(3) # outputs 8 

これはPythonicを感じていますか?

+0

Pythonic * per se *ではないデリゲートによく似ていますが、このようにしても問題はありません。 –

+0

ええ、これについてもう少し考えていたら、C#の代理人のようなものだと思っていました。おそらく、私は実際にクラスに「デリゲート」という名前を付ける必要があります。 – kindall

+0

これについてもっと考えてみると、私は本当にメタクラスを必要としません。私はもともとクラスをインスタンス化せずに機能を手に入れたいと思っていましたが、今作成した方法では、おそらくインスタンス化で理解できるでしょう。おそらくもう少しの仕事が必要です。 – kindall

6

ユーザーが書き込むことができるようにデコレータを使用することがあります。

あなたのモジュールで次に
@new_factory 
def myFactory(cls, *args, **kwargs): 
    instance = myFactory.chain(cls, *args, **kwargs) 
    # do something with the instance here if desired 
    return instance 

import sys 

def new_factory(f): 
    mod = sys.modules[__name__] 
    f.chain = mod.factory 
    mod.factory = f 
    return f 
+0

これは興味深いアプローチです。私は 'chain'呼び出しを行うラッパーを追加することができるので、ユーザーはその部分を書く必要がありません。また、関数が定義されている以外の時にコールバック関数を追加したいユースケースもうまくサポートしています。 – kindall

11

は少しさらにaaronsterling's ideaを取る:

デコレータを使用してのアーロンのアイデアとのリストを保持するクラスのイグナシオのアイデアを組み合わせる
class C(object): 
    _oncreate = [] 

    def __new__(cls): 
    return reduce(lambda x, y: y(x), cls._oncreate, super(C, cls).__new__(cls)) 

    @classmethod 
    def oncreate(cls, func): 
    cls._oncreate.append(func) 

c = C() 
print hasattr(c, 'spew') 

@C.oncreate 
def spew(obj): 
    obj.spew = 42 
    return obj 

c = C() 
print c.spew 
+0

これは、クラスをインスタンス化する特定のユースケースのための本当にエレガントなアプローチです。 – kindall

+0

通常の方法でも全く同じことができます。 –

関連する問題