web-dev-qa-db-ja.com

クラス内のすべての関数にデコレーターを接続する

私は本当にこれを行う必要はありませんが、疑問に思っていました。すべての関数に対して明示的に宣言するのではなく、クラス内のすべての関数にデコレータをバインドする方法はありますか。

私はそれがデコレータというよりむしろ一種のアスペクトになると思います、そしてそれは少し奇妙に感じますが、タイミングや認証のようなものを考えていて、それはかなりきちんとしているでしょう。

51
dochead

これを行う、またはクラス定義に他の変更を加える最もクリーンな方法は、メタクラスを定義することです。

または、クラス定義の最後にデコレーターを適用するだけです。

class Something:
   def foo(self): pass

for name, fn in inspect.getmembers(Something):
    if isinstance(fn, types.UnboundMethodType):
        setattr(Something, name, decorator(fn))

Python 3の場合、types.UnboundMethodTypeをtypes.FunctionTypeに置き換えます。

もちろん実際には、デコレータをより選択的に適用する必要があります。1つのメソッドを除いてすべてをデコレートしたい場合は、従来の方法でデコレータ構文を使用するだけの方が簡単で柔軟であることがわかります。

30
Duncan

クラス定義の変更を考えるたびに、クラスデコレータまたはメタクラスを使用できます。例えばメタクラスを使用する

import types

class DecoMeta(type):
   def __new__(cls, name, bases, attrs):

      for attr_name, attr_value in attrs.iteritems():
         if isinstance(attr_value, types.FunctionType):
            attrs[attr_name] = cls.deco(attr_value)

      return super(DecoMeta, cls).__new__(cls, name, bases, attrs)

   @classmethod
   def deco(cls, func):
      def wrapper(*args, **kwargs):
         print "before",func.func_name
         result = func(*args, **kwargs)
         print "after",func.func_name
         return result
      return wrapper

class MyKlass(object):
   __metaclass__ = DecoMeta

   def func1(self): 
      pass

MyKlass().func1()

出力:

before func1
after func1

注:staticmethodとclassmethodを修飾しません

28
Anurag Uniyal

もちろん、メタクラスは、pythonがオブジェクトを作成する方法を変更したいときに、最もPython的な方法です。これは、クラスの___new___メソッドをオーバーライドすることで実行できます。 。しかし、この問題(特にpython 3.X)の場合)については、いくつか触れておきたいことがあります。

  1. _types.FunctionType_は関数型であるため、装飾されないように特別なメソッドを保護しません。より一般的な方法として、名前が開始されていないオブジェクトを2つのアンダースコア(____)で装飾することができます。このメソッドのもう1つの利点は、名前空間に存在し、____で始まるが___qualname___、___module___などの関数ではないオブジェクトもカバーすることです。
  2. ___new___のヘッダーのnamespace引数には、___init___内のクラス属性が含まれていません。その理由は、___new___が___init___(初期化中)の前に実行されるためです。

  3. ほとんどの場合、別のモジュールからデコレータをインポートするため、classmethodをデコレータとして使用する必要はありません。

  4. クラスにグローバルアイテム(___init___の外)が含まれている場合、名前が____で始まっていないかどうかのチェックと一緒に装飾されるのを拒否するために、_types.FunctionType_でタイプをチェックできます非関数オブジェクトを装飾していないことを確認してください。

使用できるサンプルmetacalsは次のとおりです。

_class TheMeta(type):
    def __new__(cls, name, bases, namespace, **kwds):
        # if your decorator is a class method of the metaclass  use
        # `my_decorator = cls.my_decorator` in order to invoke the decorator.
        namespace = {k: v if k.startswith('__') else my_decorator(v) for k, v in namespace.items()}
        return type.__new__(cls, name, bases, namespace)
_

デモ:

_def my_decorator(func):
        def wrapper(self, arg):
            # You can also use *args instead of (self, arg) and pass the *args
            # to the function in following call.
            return "the value {} gets modified!!".format(func(self, arg))
        return wrapper


class TheMeta(type):
    def __new__(cls, name, bases, namespace, **kwds):
        # my_decorator = cls.my_decorator (if the decorator is a classmethod)
        namespace = {k: v if k.startswith('__') else my_decorator(v) for k, v in namespace.items()}
        return type.__new__(cls, name, bases, namespace)


class MyClass(metaclass=TheMeta):
    # a = 10
    def __init__(self, *args, **kwargs):
        self.item = args[0]
        self.value = kwargs['value']

    def __getattr__(self, attr):
        return "This class hasn't provide the attribute {}.".format(attr)

    def myfunction_1(self, arg):
        return arg ** 2

    def myfunction_2(self, arg):
        return arg ** 3

myinstance = MyClass(1, 2, value=100)
print(myinstance.myfunction_1(5))
print(myinstance.myfunction_2(2))
print(myinstance.item)
print(myinstance.p)
_

出力:

_the value 25 gets modified!!
the value 8 gets modified!!
1
This class hasn't provide the attribute p. # special method is not decorated.
_

前述のメモの3番目の項目をチェックするには、行_a = 10_のコメントを外してprint(myinstance.a)を実行し、結果を確認してから、次のように___new___の辞書内包を変更して、もう一度結果を確認します。 :

_namespace = {k: v if k.startswith('__') and not isinstance(v, types.FunctionType)\
             else my_decorator(v) for k, v in namespace.items()}
_
1
Kasrâmvd

Python 3の更新:

class DecoMeta(type):
   def __new__(cls, name, bases, attrs):

      for attr_name, attr_value in attrs.items():
         if isinstance(attr_value, types.FunctionType) :
            attrs[attr_name] = cls.deco(attr_value)

      return super(DecoMeta, cls).__new__(cls, name, bases, attrs)

   @classmethod
   def deco(cls, func):
      def wrapper(*args, **kwargs):
         print ("before",func.__name__)
         result = func(*args, **kwargs)
         print ("after",func.__name__)
         return result
      return wrapper

(そして、このためにダンカンに感謝します)

0
Doha Simon

次のコードはpython2.xおよび3.xで機能します

-プラバカール

import inspect

def decorator_for_func(orig_func):
    "Decorate a function to print a message before and after execution."
    def decorator(*args, **kwargs):
         "Print message before and after a function call."
         print("Decorating wrapper called for method %s" % orig_func.__name__)
         result = orig_func(*args, **kwargs)
         return result
    return decorator

def decorator_for_class(cls):
    for name, method in inspect.getmembers(cls):
        if (not inspect.ismethod(method) and not inspect.isfunction(method)) or inspect.isbuiltin(method):
            continue
        print("Decorating function %s" % name)
        setattr(cls, name, decorator_for_func(method))
    return cls

@decorator_for_class
class decorated_class:
     def method1(self, arg, **kwargs):
         print("Method 1 called with arg %s" % arg)
     def method2(self, arg):
         print("Method 2 called with arg %s" % arg)


d=decorated_class()
d.method1(1, a=10)
d.method2(2)
0

場合によっては、やや似ていることもあります。場合によっては、すべてのクラスではなく、デバッグなどのアタッチメントをトリガーする必要がありますが、オブジェクトのすべてのメソッドについて、それが何をしているかの記録が必要になる場合があります。

def start_debugging():
        import functools
        import datetime
        filename = "debug-{date:%Y-%m-%d_%H_%M_%S}.txt".format(date=datetime.datetime.now())
        debug_file = open(filename, "a")
        debug_file.write("\nDebug.\n")

        def debug(func):
            @functools.wraps(func)
            def wrapper_debug(*args, **kwargs):
                args_repr = [repr(a) for a in args]  # 1
                kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]  # 2
                signature = ", ".join(args_repr + kwargs_repr)  # 3
                debug_file.write(f"Calling {func.__name__}({signature})\n")
                value = func(*args, **kwargs)
                debug_file.write(f"{func.__name__!r} returned {value!r}\n")  # 4
                debug_file.flush()
                return value
            return wrapper_debug

        for obj in (self):
            for attr in dir(obj):
                if attr.startswith('_'):
                    continue
                fn = getattr(obj, attr)
                if not isinstance(fn, types.FunctionType) and \
                        not isinstance(fn, types.MethodType):
                    continue
                setattr(obj, attr, debug(fn))

この関数は、いくつかのオブジェクト(現在はselfのみ)を通過し、_で始まらないすべての関数とメソッドをデバッグデコレーターで置き換えます。

Dir(self)を繰り返すだけのこれに使用される方法は、上記では扱われていませんが、完全に機能します。また、オブジェクトの外部から呼び出すことも、はるかに任意に呼び出すこともできます。

0
Tatarize