Python Language
デコレータ
サーチ…
前書き
デコレータ機能はソフトウェア設計パターンです。それらは、サブクラスを直接使用したり、装飾された関数のソースコードを変更することなく、関数、メソッド、またはクラスの機能を動的に変更します。デコレータを正しく使用すると、開発プロセスで強力なツールになることがあります。このトピックでは、Pythonでのデコレータ関数の実装とアプリケーションについて説明します。
構文
def decorator_function(f):pass#は、decorator_functionという名前のデコレータを定義します。
@decorator_function
def decorated_function():pass#関数は現在、デコレータ関数をラップ(装飾)しています。decorated_function = decorator_function(decorated_function)#これは構文的な砂糖
@decorator_function
を使うのと同じ@decorator_function
パラメーター
パラメータ | 詳細 |
---|---|
f | 装飾(ラップ)される関数は、 |
デコレータ機能
デコレータは、他の関数やメソッドの動作を拡張します。関数をパラメータとして受け取り、拡張された関数を返す関数はデコレータとして使用できます。
# This simplest decorator does nothing to the function being decorated. Such
# minimal decorators can occasionally be used as a kind of code markers.
def super_secret_function(f):
return f
@super_secret_function
def my_function():
print("This is my secret function.")
@
-notationは以下と等価であるシンタックスシュガーです。
my_function = super_secret_function(my_function)
デコレータの仕組みを理解するためには、これを念頭におくことが重要です。この "unsugared"構文は、デコレータ関数が引数として関数をとり、なぜそれが別の関数を返すのかを明確にしています。また、関数を返さないと何が起こるかを示します:
def disabled(f):
"""
This function returns nothing, and hence removes the decorated function
from the local scope.
"""
pass
@disabled
def my_function():
print("This function can no longer be called...")
my_function()
# TypeError: 'NoneType' object is not callable
したがって、通常、デコレータ内に新しい関数を定義して返します。この新しい関数は、最初に行う必要があることを行い、次に元の関数を呼び出し、最後に戻り値を処理します。元の関数が受け取った引数を出力し、それを呼び出す単純なデコレータ関数を考えてみましょう。
#This is the decorator
def print_args(func):
def inner_func(*args, **kwargs):
print(args)
print(kwargs)
return func(*args, **kwargs) #Call the original function with its arguments.
return inner_func
@print_args
def multiply(num_a, num_b):
return num_a * num_b
print(multiply(3, 5))
#Output:
# (3,5) - This is actually the 'args' that the function receives.
# {} - This is the 'kwargs', empty because we didn't specify keyword arguments.
# 15 - The result of the function.
デコレータクラス
序文で述べたように、デコレータは、その動作を補うために別の関数に適用できる関数です。構文砂糖は、 my_func = decorator(my_func)
と等価です。しかし、 decorator
が代わりにクラスの場合はどうなりますか?構文はまだ機能しますが、 my_func
がdecorator
クラスのインスタンスに置き換えられるmy_func
がmy_func
ます。このクラスが__call__()
マジックメソッドを実装している場合、関数であるかのようにmy_func
を使用することは可能です。
class Decorator(object):
"""Simple decorator class."""
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('Before the function call.')
res = self.func(*args, **kwargs)
print('After the function call.')
return res
@Decorator
def testfunc():
print('Inside the function.')
testfunc()
# Before the function call.
# Inside the function.
# After the function call.
クラスデコレータで装飾された関数は、タイプチェックの観点からはもはや「関数」とはみなされないことに注意してください。
import types
isinstance(testfunc, types.FunctionType)
# False
type(testfunc)
# <class '__main__.Decorator'>
装飾方法
__get__
するメソッドの場合、追加の__get__
-methodを定義する必要があります:
from types import MethodType
class Decorator(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('Inside the decorator.')
return self.func(*args, **kwargs)
def __get__(self, instance, cls):
# Return a Method if it is called on an instance
return self if instance is None else MethodType(self, instance)
class Test(object):
@Decorator
def __init__(self):
pass
a = Test()
デコレータの中に。
警告!
クラスデコレータは特定の関数に対して1つのインスタンスしか生成しないので、クラスデコレータでメソッドを修飾すると、そのクラスのすべてのインスタンス間で同じデコレータが共有されます。
from types import MethodType
class CountCallsDecorator(object):
def __init__(self, func):
self.func = func
self.ncalls = 0 # Number of calls of this method
def __call__(self, *args, **kwargs):
self.ncalls += 1 # Increment the calls counter
return self.func(*args, **kwargs)
def __get__(self, instance, cls):
return self if instance is None else MethodType(self, instance)
class Test(object):
def __init__(self):
pass
@CountCallsDecorator
def do_something(self):
return 'something was done'
a = Test()
a.do_something()
a.do_something.ncalls # 1
b = Test()
b.do_something()
b.do_something.ncalls # 2
デコレータを装飾された機能のように見せる
デコレータは通常、機能メタデータを同じではないため取り除きます。これは、メタプログラミングを使用して機能メタデータに動的にアクセスするときに問題を引き起こす可能性があります。メタデータには、関数のドキュメントストリングとその名前も含まれます。 functools.wraps
は、いくつかの属性をラッパー関数にコピーすることによって、装飾された関数を元の関数のように見せます。
from functools import wraps
デコレータをラップする2つの方法は、元の関数が装飾されていることを隠すことで同じことを達成しています。すでにバージョンを使用していない限り、クラスバージョンに関数バージョンを優先させる理由はありません。
関数として
def decorator(func):
# Copies the docstring, name, annotations and module to the decorator
@wraps(func)
def wrapped_func(*args, **kwargs):
return func(*args, **kwargs)
return wrapped_func
@decorator
def test():
pass
test.__name__
'テスト'
クラスとして
class Decorator(object):
def __init__(self, func):
# Copies name, module, annotations and docstring to the instance.
self._wrapped = wraps(func)(self)
def __call__(self, *args, **kwargs):
return self._wrapped(*args, **kwargs)
@Decorator
def test():
"""Docstring of test."""
pass
test.__doc__
'テストの皮ひも'
引数を持つデコレータ(デコレータファクトリ)
デコレータはただ一つの引数、つまり装飾される関数をとります。他の引数を渡す方法はありません。
しかし、追加の議論がしばしば望まれる。そのトリックは、任意の引数をとり、デコレータを返す関数を作ることです。
デコレータ関数
def decoratorfactory(message):
def decorator(func):
def wrapped_func(*args, **kwargs):
print('The decorator wants to tell you: {}'.format(message))
return func(*args, **kwargs)
return wrapped_func
return decorator
@decoratorfactory('Hello World')
def test():
pass
test()
デコレータがあなたに伝えたいのは、Hello World
重要な注意点:
このようなデコレータファクトリでは、カッコで一対のデコレータを呼び出す必要があります。
@decoratorfactory # Without parentheses
def test():
pass
test()
TypeError:decorator()missing 1必要な位置指定引数: 'func'
デコレータクラス
def decoratorfactory(*decorator_args, **decorator_kwargs):
class Decorator(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('Inside the decorator with arguments {}'.format(decorator_args))
return self.func(*args, **kwargs)
return Decorator
@decoratorfactory(10)
def test():
pass
test()
引数付きのデコレータの内側(10、)
デコレータでシングルトンクラスを作成する
シングルトンは、クラスのインスタンス化を1つのインスタンス/オブジェクトに制限するパターンです。デコレータを使用して、クラスを既存のインスタンスを返すか、新しいインスタンスを作成する(存在しない場合)ように、クラスをシングルトンとして定義できます。
def singleton(cls):
instance = [None]
def wrapper(*args, **kwargs):
if instance[0] is None:
instance[0] = cls(*args, **kwargs)
return instance[0]
return wrapper
このデコレータは、どのクラス宣言にも追加することができ、クラスのインスタンスが最大で1つ作成されることを保証します。それ以降の呼び出しでは、既存のクラスインスタンスが返されます。
@singleton
class SomeSingletonClass:
x = 2
def __init__(self):
print("Created!")
instance = SomeSingletonClass() # prints: Created!
instance = SomeSingletonClass() # doesn't print anything
print(instance.x) # 2
instance.x = 3
print(SomeSingletonClass().x) # 3
したがって、ローカル変数を使用してクラスインスタンスを参照するかどうか、または別の「インスタンス」を作成するかどうかは関係なく、常に同じオブジェクトを取得します。
デコレータを使って関数を時間を計る
import time
def timer(func):
def inner(*args, **kwargs):
t1 = time.time()
f = func(*args, **kwargs)
t2 = time.time()
print 'Runtime took {0} seconds'.format(t2-t1)
return f
return inner
@timer
def example_function():
#do stuff
example_function()