私は本当にこれを行う必要はありませんが、疑問に思っていました。すべての関数に対して明示的に宣言するのではなく、クラス内のすべての関数にデコレータをバインドする方法はありますか。
私はそれがデコレータというよりむしろ一種のアスペクトになると思います、そしてそれは少し奇妙に感じますが、タイミングや認証のようなものを考えていて、それはかなりきちんとしているでしょう。
これを行う、またはクラス定義に他の変更を加える最もクリーンな方法は、メタクラスを定義することです。
または、クラス定義の最後にデコレーターを適用するだけです。
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つのメソッドを除いてすべてをデコレートしたい場合は、従来の方法でデコレータ構文を使用するだけの方が簡単で柔軟であることがわかります。
クラス定義の変更を考えるたびに、クラスデコレータまたはメタクラスを使用できます。例えばメタクラスを使用する
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を修飾しません
もちろん、メタクラスは、pythonがオブジェクトを作成する方法を変更したいときに、最もPython的な方法です。これは、クラスの___new__
_メソッドをオーバーライドすることで実行できます。 。しかし、この問題(特にpython 3.X)の場合)については、いくつか触れておきたいことがあります。
types.FunctionType
_は関数型であるため、装飾されないように特別なメソッドを保護しません。より一般的な方法として、名前が開始されていないオブジェクトを2つのアンダースコア(___
_)で装飾することができます。このメソッドのもう1つの利点は、名前空間に存在し、___
_で始まるが___qualname__
_、___module__
_などの関数ではないオブジェクトもカバーすることです。___new__
_のヘッダーのnamespace
引数には、___init__
_内のクラス属性が含まれていません。その理由は、___new__
_が___init__
_(初期化中)の前に実行されるためです。
ほとんどの場合、別のモジュールからデコレータをインポートするため、classmethod
をデコレータとして使用する必要はありません。
__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()}
_
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
(そして、このためにダンカンに感謝します)
次のコードは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)
場合によっては、やや似ていることもあります。場合によっては、すべてのクラスではなく、デバッグなどのアタッチメントをトリガーする必要がありますが、オブジェクトのすべてのメソッドについて、それが何をしているかの記録が必要になる場合があります。
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)を繰り返すだけのこれに使用される方法は、上記では扱われていませんが、完全に機能します。また、オブジェクトの外部から呼び出すことも、はるかに任意に呼び出すこともできます。