2009-08-17 13 views
13

私は、Pythonの関数とメソッドの両方で使用できるデコレータを作成しようとしています。これはそれ自身では難しいことではありませんが、引数を取るデコレータを作成するときは、そうであるようです。関数とメソッドで同じデコレータ(引数付き)を使用する

class methods(object): 
    def __init__(self, *_methods): 
     self.methods = _methods 

    def __call__(self, func): 
     def inner(request, *args, **kwargs): 
      print request 
      return func(request, *args, **kwargs) 
     return inner 

    def __get__(self, obj, type=None): 
     if obj is None: 
      return self 
     new_func = self.func.__get__(obj, type) 
     return self.__class__(new_func) 

上記のコードが正しく関数/メソッドをラップするが、この方法の場合には、request引数は、上で動作している場合ではなく、最初の非自己引数です。

デコレータがメソッドではなく関数に適用されているかどうかを判断する方法はありますか?

答えて

12

__get__アプローチで展開してください。これは一般化してデコレータデコレータにすることができます。あなたは自分のデコレータが自動的にそれはで使用されている条件に適応することができます。このように

class _MethodDecoratorAdaptor(object): 
    def __init__(self, decorator, func): 
     self.decorator = decorator 
     self.func = func 
    def __call__(self, *args, **kwargs): 
     return self.decorator(self.func)(*args, **kwargs) 
    def __get__(self, instance, owner): 
     return self.decorator(self.func.__get__(instance, owner)) 

def auto_adapt_to_methods(decorator): 
    """Allows you to use the same decorator on methods and functions, 
    hiding the self argument from the decorator.""" 
    def adapt(func): 
     return _MethodDecoratorAdaptor(decorator, func) 
    return adapt 

def allowed(*allowed_methods): 
    @auto_adapt_to_methods 
    def wrapper(func): 
     def wrapped(request): 
      if request not in allowed_methods: 
       raise ValueError("Invalid method %s" % request) 
      return func(request) 
     return wrapped 
    return wrapper 

ラッパー関数は、すべての関数呼び出しで呼び出され、そうしないことに注意してくださいそこに高価なもの。デコレータの

使用法:ここで

class Foo(object): 
    @allowed('GET', 'POST') 
    def do(self, request): 
     print "Request %s on %s" % (request, self) 

@allowed('GET') 
def do(request): 
    print "Plain request %s" % request 

Foo().do('GET') # Works 
Foo().do('POST') # Raises 
+0

'_MethodDecoratorAdaptor .__ init__'の*先頭に' update_wrapper(self、func) 'を追加するべきです(ここで、update_wrapperはfunからですctoolsモジュール)。これにより、結果として得られるデコレータは、デコレータが作成した関数/呼び出し可能関数のカスタム属性を保持しながら、それらを合成可能に保ちます。 – spookylukey

+5

私は、この方法がいくつかの状況でのみ機能し、うまく動作しないときにデバッグするのが非常に難しいことを発見しました。 http://groups.google.com/group/django-developers/msg/f36976f5cfbcbeb3 – spookylukey

+0

@spookylukey実際にはこれがDjangoで処理される方法はかなりきれいです。 – astrojuanlu

4

デコレータは常に関数オブジェクトに適用されます。デコレータprintにその引数の型を持たせると、そのことを確認できます。後者には例外がありますが、一般的に関数オブジェクトも返すべきです(これはすでに適切な__get__! - を持つデコレータです)。

コードではすなわち、:

class X(object): 

    @deco 
    def f(self): pass 

deco(f)fが関数ではなく、メソッドの型のインスタンスである、あなたはまだそこにいる間、クラス本体内と呼ばれ、。 (後でfXの属性またはそのインスタンスとしてアクセスされるときに、この方法が製造され、の__get__に返されます)。

あなたのデコレータに欲しいと思うおもちゃの使い方を説明することができます。

編集:何でも呼び出し可能に引数として渡されたときに、これは引数を持つデコレーターのために行く、あまりにも、すなわち

class X(object): 

    @deco(23) 
    def f(self): pass 

それはクラス本体で呼ばれていますdeco(23)(f)だ、fはまだ関数オブジェクトであるdeco(23)その呼び出し可能関数は関数オブジェクトを返す必要があります(一般的には例外を除いて;-))。

4

バインドメソッドでデコレータを使用するために既に__get__を定義しているので、メソッドまたは関数で使用されているかどうかを示すフラグを渡すことができます。

class methods(object): 
    def __init__(self, *_methods, called_on_method=False): 
     self.methods = _methods 
     self.called_on_method 

    def __call__(self, func): 
     if self.called_on_method: 
      def inner(self, request, *args, **kwargs): 
       print request 
       return func(request, *args, **kwargs) 
     else: 
      def inner(request, *args, **kwargs): 
       print request 
       return func(request, *args, **kwargs) 
     return inner 

    def __get__(self, obj, type=None): 
     if obj is None: 
      return self 
     new_func = self.func.__get__(obj, type) 
     return self.__class__(new_func, called_on_method=True) 
1

私は例外処理に依存しているが出ている部分(具体的な)ソリューション。特定のHttpRequestメソッドのみを許可するデコレータを作成しようとしていますが、ビューである関数とビューであるメソッドの両方で機能させるようにしています。ここで

class methods(object): 
    def __init__(self, *_methods): 
     self.methods = _methods 

    def __call__(self, func): 
     @wraps(func) 
     def inner(*args, **kwargs): 
      try: 
       if args[0].method in self.methods: 
        return func(*args, **kwargs) 
      except AttributeError: 
       if args[1].method in self.methods: 
        return func(*args, **kwargs) 
      return HttpResponseMethodNotAllowed(self.methods) 
     return inner 

は、2つの使用例です:

ので、このクラスでは、私がやりたいだろうクラスの

@methods("GET") 
def view_func(request, *args, **kwargs): 
    pass 

と飾る方法:

class ViewContainer(object): 
    # ... 

    @methods("GET", "PUT") 
    def object(self, request, pk, *args, **kwargs): 
     # stuff that needs a reference to self... 
     pass 
機能を飾ります

例外処理を使用するよりも優れたソリューションはありますか?

0

は私が飾ら呼び出し可能な関数またはメソッドであるかどうかを検出することが分かっ一般的な方法である:

import functools 

class decorator(object): 

    def __init__(self, func): 
    self._func = func 
    self._obj = None 
    self._wrapped = None 

    def __call__(self, *args, **kwargs): 
    if not self._wrapped: 
     if self._obj: 
     self._wrapped = self._wrap_method(self._func) 
     self._wrapped = functools.partial(self._wrapped, self._obj) 
     else: 
     self._wrapped = self._wrap_function(self._func) 
    return self._wrapped(*args, **kwargs) 

    def __get__(self, obj, type=None): 
    self._obj = obj 
    return self 

    def _wrap_method(self, method): 
    @functools.wraps(method) 
    def inner(self, *args, **kwargs): 
     print('Method called on {}:'.format(type(self).__name__)) 
     return method(self, *args, **kwargs) 
    return inner 

    def _wrap_function(self, function): 
    @functools.wraps(function) 
    def inner(*args, **kwargs): 
     print('Function called:') 
     return function(*args, **kwargs) 
    return inner 

使用例:

class Foo(object): 
    @decorator 
    def foo(self, foo, bar): 
    print(foo, bar) 

@decorator 
def foo(foo, bar): 
    print(foo, bar) 

foo(12, bar=42)  # Function called: 12 42 
foo(12, 42)   # Function called: 12 42 
obj = Foo() 
obj.foo(12, bar=42) # Method called on Foo: 12 42 
obj.foo(12, 42)  # Method called on Foo: 12 42 
関連する問題