2011-09-20 21 views
11

Pythonでクラスのデコレータにオプションの引数を渡そうとしています。私は現在持っているコードの下 :Pythonクラスのデコレータの引数

class Cache(object): 
    def __init__(self, function, max_hits=10, timeout=5): 
     self.function = function 
     self.max_hits = max_hits 
     self.timeout = timeout 
     self.cache = {} 

    def __call__(self, *args): 
     # Here the code returning the correct thing. 


@Cache 
def double(x): 
    return x * 2 

@Cache(max_hits=100, timeout=50) 
def double(x): 
    return x * 2 

デフォルトの1(私の__init__機能でmax_hits=10, timeout=5)を上書きする引数と第二デコレータは、動作していないと私は例外TypeError: __init__() takes at least 2 arguments (3 given)を得ました。私は多くのソリューションを試して記事を読んでいましたが、ここではまだそれを動作させることはできません。

これを解決する方法はありますか?ありがとう!

答えて

12

@Cache(max_hits=100, timeout=50)__init__(max_hits=100, timeout=50)を呼び出しているため、function引数を満たしていません。

関数が存在するかどうかを検出するラッパーメソッドを使用してデコレータを実装できます。関数を見つけると、Cacheオブジェクトを返すことができます。それ以外の場合は、デコレータとして使用されるラッパー関数を返すことができます。

class _Cache(object): 
    def __init__(self, function, max_hits=10, timeout=5): 
     self.function = function 
     self.max_hits = max_hits 
     self.timeout = timeout 
     self.cache = {} 

    def __call__(self, *args): 
     # Here the code returning the correct thing. 

# wrap _Cache to allow for deferred calling 
def Cache(function=None, max_hits=10, timeout=5): 
    if function: 
     return _Cache(function) 
    else: 
     def wrapper(function): 
      return _Cache(function, max_hits, timeout) 

     return wrapper 

@Cache 
def double(x): 
    return x * 2 

@Cache(max_hits=100, timeout=50) 
def double(x): 
    return x * 2 
+0

お使いのソリューションのおかげでみんなと@ lunixbochs!魅力のように働く:) – Dachmt

+3

開発者がキーワード引数の代わりに 'Cache'をキーワードとして呼び出すと(' @Cache(100,50) 'など)、' function'には値100が、 'max_hits'には50が割り当てられます。関数が呼び出されるまでエラーは発生しません。これは、ほとんどの人が均一な位置とキーワードのセマンティクスを期待しているので、驚くべき動作と考えることができます。 – unutbu

11
@Cache 
def double(...): 
    ... 

@Cache(max_hits=100, timeout=50) 
def double(...): 
    ... 

def double(...): 
    ... 
double = Cache(max_hits=100, timeout=50)(double) 

と同等であるが

def double(...): 
    ... 
double=Cache(double) 

と等価ですは、Cache(double)とは非常に異なるセマンティックスを持っています。

Cacheを両方の使用例に対応させることは賢明ではありません。

代わりに、オプションmax_hitstimeout引数を取ることができデコレータ・ファクトリーを使用して、デコレータを返すことができます:

class Cache(object): 
    def __init__(self, function, max_hits=10, timeout=5): 
     self.function = function 
     self.max_hits = max_hits 
     self.timeout = timeout 
     self.cache = {} 

    def __call__(self, *args): 
     # Here the code returning the correct thing. 

def cache_hits(max_hits=10, timeout=5): 
    def _cache(function): 
     return Cache(function,max_hits,timeout) 
    return _cache 

@cache_hits() 
def double(x): 
    return x * 2 

@cache_hits(max_hits=100, timeout=50) 
def double(x): 
    return x * 2 

PSを。 Cacheクラスに__init____call__以外のメソッドがない場合は、_cache関数内のすべてのコードを移動してCacheを完全に削除することができます。

+1

賢明でないか...開発者が誤ってcache()の代わりに@cacheを使用した場合、結果の関数を呼び出すときに奇妙なエラーが発生します。他の実装は実際にはキャッシュとキャッシュの両方として動作します – lunixbochs

+0

ありがとう@unutbu、良い解決法もあります。 – Dachmt

+1

@lunixbochs: 'cache_hits'(nee' cache')と 'cache_hits() 'を混同している開発者は、関数オブジェクトを関数呼び出しと混同したり、ジェネレータをイテレータで間違えたりする可能性があります。適度に経験豊富なPythonプログラマでも、差分に注意を払うのに慣れるべきです。 – unutbu

0

私はこの質問から多くのことを学びました。ありがとうございます。最初の@Cacheに空の括弧を入れるだけの答えはありませんか?次に、functionパラメータを__call__に移動できます。

class Cache(object): 
    def __init__(self, max_hits=10, timeout=5): 
     self.max_hits = max_hits 
     self.timeout = timeout 
     self.cache = {} 

    def __call__(self, function, *args): 
     # Here the code returning the correct thing. 

@Cache() 
def double(x): 
    return x * 2 

@Cache(max_hits=100, timeout=50) 
def double(x): 
    return x * 2 

私は、このアプローチは、よりシンプルで簡潔だと思いますが。

def cache(max_hits=10, timeout=5): 
    def caching_decorator(fn): 
     def decorated_fn(*args ,**kwargs): 
      # Here the code returning the correct thing. 
     return decorated_fn 
    return decorator 

デコレータを使用しているときは、括弧を忘れてしまった場合は、残念ながらまだ外として、実行時までエラーを得ることはありませんデコレータパラメータは、デコレートしようとしている関数に渡されます。そして、実行時に内部デコレータは文句:

TypeError: caching_decorator() takes exactly 1 argument (0 given).

あなたはデコレータのパラメータは呼び出し可能になるだろうことはありません知っている場合は、これをキャッチすることができますしかし:

def cache(max_hits=10, timeout=5): 
    assert not callable(max_hits), "@cache passed a callable - did you forget to parenthesize?" 
    def caching_decorator(fn): 
     def decorated_fn(*args ,**kwargs): 
      # Here the code returning the correct thing. 
     return decorated_fn 
    return decorator 

をあなたが今しようとした場合:

@cache 
def some_method() 
    pass 

宣言にはAssertionErrorが表示されます。

合計で、この記事では、装飾するクラスではなく、クラスをデコレートするデコレータを探していました。他の誰かがそうであれば、this questionが便利です。

関連する問題