この記事では、Pythonのデコレーターの使い方を解説します。デコレーターとは、関数をデコる(死語?)ための仕組みです。
もう少し真面目に説明すると、関数にデコレーターを付けることで、その関数を実行した際にデコレーター定義関数に実行した関数を渡し、デコレーター定義関数を実行します。
言葉ではわかりにくいと思うのでサンプルを見てみましょう。試しに以下のtest関数にデコレータを修飾してみます。
def test():
print('test関数')
この関数にデコレーターを使って修飾してみます。
# デコレーター定義関数
def decorator(func):
def wrapper(*args, **kwargs):
print('decorator start')
func(*args, **kwargs)
print('decorator end')
return wrapper
@decorator
def test():
print('test関数')
test()
# decorator start
# test関数
# decorator end
デコレーター定義関数を作成し、test関数を修飾しています。それにより、test関数を実行した際に、decorator関数が処理されているのがわかります。このように、デコレーター定義関数を作成し、関数にデコレーターを付与することで、その関数の実行時にデコレーター定義関数を実行します。
それでは、デコレーターの使い方を見ていきましょう!
デコレーターの使い方

デコレーター定義関数は以下のように定義します。wrapperという関数名に構文的な意味はありません。
def デコレーター名(func):
def wrapper(*args, **kw):
# 処理
return wrapper
このように作成したデコレーターを以下のように関数に付与します。
@デコレーター名
def 何らかの関数():
# 処理
デコレーターが付与された関数は、実行時にデコレーター定義関数の引数funcに渡されてデコレーター定義関数が実行されます。
同等の呼び出し
簡単な処理の関数を2つ作成し、1つにはデコレーターを付与した。この2つの関数が同じ結果になるように呼び出してみます。
def deco(func):
def wrapper(*args, **kw):
print('deco - start')
func(*args, **kw)
print('deco - end')
return wrapper
@deco
def func1(text):
print(text)
def func2(text):
print(text)
# デコレーター有り
func1('func1')
# deco - start
# func1
# deco - end
# デコレーター無し
deco(func2)('func2')
# deco - start
# func2
# deco - end
25行目の呼び出し方をすることで同等の処理結果となる。何だか紛らわしいが、deco関数を実行するとwrapper関数を返します。返されたwrapper関数に対して(‘func2’)を引数として渡し、実行します。
引数を使用するデコレーター
引数が定義されている関数にデコレーターを付与させることもできます。通常の引数と可変長引数を定義した関数にデコレーターを付け、動作を確認してみます。
def decorator(func):
def wrapper(*args, **kw):
func(*args, **kw)
return wrapper
# 通常の引数と可変長引数を取る関数
@decorator
def test(text, *args, **kw):
print(text)
print(args)
print(kw)
test('test関数です', 1, 2, 3, one=1, two=2, three=3)
# test関数です
# (1, 2, 3)
# {'one': 1, 'two': 2, 'three': 3}
3行目でfuncの引数に*argsと**kwを指定しています。これによって、引数を正常に引き渡せています。
アンパック
リストなどにアスタリスクをつけることで要素を分解して渡すことができます。
args = ('arg', 1, 2, 3)
print(*args)
# arg 1 2 3
これは以下のコードと同等です。
print(args[0], args[1], args[2], args[3])

戻り値を返すデコレーター
戻り値が定義されている関数のデコレーターの定義方法を見ていきます。
def decorator(func):
def wrapper(*args, **kw):
result = func(*args, **kw)
twice = result * 2
return result, twice
return wrapper
@decorator
def add(x, y):
return x + y
r = add(1, 2)
print(r)
# (3, 6)
wrapper関数内でfunc関数の戻り値を返すことで戻り値を定義できました。返す前に値を加工することもでき、これによりadd関数の結果を加工して、全く別物として返すこともできます。
デコレーターに引数を渡す
デコレーターに引数を渡して、デコレーター定義関数内で使うことができます。
以下のコードは、関数のスピードを測るデコレーターです。デコレーターに指定した回数だけ繰り返し処理され、その平均値を返します。
import time
def func_speed(count):
def _func_speed(func):
def wrapper(*args, **kw):
t = 0
for i in range(count):
s_t = time.time()
func(*args, **kw)
t += time.time() - s_t
return t / count
return wrapper
return _func_speed
@func_speed(10)
def func(second):
# sleepで引数に指定された秒数だけ、実行を停止
time.sleep(second)
print(func(0.1))
# 0.10340278148651123
実行結果が約0.1秒だったので正常に速度を測れているのがわかります。しかし、このままではデコレーターに引数を渡さなかった場合に正常に動作しません。
なので、デコレーターに引数が渡されなかった場合の処理も追加しておきます。
# 引数名をargに変更
def func_speed(arg):
def _func_speed(func):
def wrapper(*args, **kw):
t = 0
for i in range(count):
s_t = time.time()
func(*args, **kw)
t += time.time() - s_t
return t / count
return wrapper
# 追加
if isinstance(arg, int):
count = arg
return _func_speed
elif callable(arg):
count = 1
return _func_speed(arg)
14行目でargがintかどうかを判別しています。argがintならばcountにargを代入して_func_speed関数を返しています。
17行目ではargが関数かどうかを判別しています。argが関数ならばデコレーターに引数を渡していないということなので、countに1を代入し、_func_speed関数にargを引数として渡し、その実行結果を返しています。
デコレーターを複数付ける
デコレーターを複数つけることもできます。以下のコードはtest関数に複数のデコレーターを付けています。
def deco1(func):
def wrapper(*args, **kw):
print('deco1 start')
func(*args, *kw)
print('deco1 end')
return wrapper
def deco2(func):
def wrapper(*args, **kw):
print('deco2 end')
func(*args, **kw)
print('deco2 end')
return wrapper
@deco1
@deco2
def test():
print('test関数')
test()
# deco1 start
# deco2 start
# test関数
# deco2 end
# deco1 end
実行結果を見ると、初めにdeco1が処理されてfuncが実行されたらdeco2が処理されています。deco2が処理されたらtest関数が処理されているのでtest関数はdeco2のfuncに渡されているのがわかります。そして、deco2が処理し終わったらdeco1の残りの処理をしています。
同等の呼び出し
覚える必要はないですが一応参考までに…。
先ほどの複数のデコレーターを付けた関数と同等の呼び出し方法です。
deco1(deco2(test))()
うん、めちゃくちゃわかりずらい!!
デコレーターを使うことでシンプルに記述できているのがわかります。