Pythonのデコレータ「@マーク」の使い方|初心者向け徹底入門

Pythonにはデコレータというものが存在しています。Pythonのデコレータは、コードをシンプルに保ちながら機能を追加できる便利なツールですが、突然「@マーク」が出てきて混乱しやすいです。本記事では、デコレータの実装例を通してデコレータの使い方について解説します。
デコレータとは
デコレーターは関数やメソッドを書き換えずに変更を行ったり拡張したりすることが可能な機能です。既存のコードを変更せずに、デコレータを追加するだけで変更ができることが大きなメリットです。
デコレータの役割とメリット
- コードの再利用性の向上
デコレータを使うことで各関数で共通する処理を何度も書く手間を減らすことができます - 可読性の向上
例えば、入力値チェックなどをデコレーターで行うことで、関数の本質的な部分に集中して作成することが可能です。- 例:エラーチェックやログ出力、処理時間計測などを関数に追加
デコレータを使ってみる
以下、簡単な例を使って実際にデコレータを使ってみます。
実装例
デコレーターの動きを知るために、関数の呼び出し前と呼び出し後にメッセージを表示するデコレータを作成して使ってみます。
プログラムはいかになります。
def my_decorator(func):
def wrapper():
print("処理を開始します")
result = func()
print("処理が完了しました")
return result
return wrapper
@my_decorator
def say_hello():
print("こんにちは!")
say_hello()
my_decorator
がデコレータ関数です。デコレータの引数は関数となります。このプログラムを実行すると以下のような出力が得られます
say_hello()
を呼び出しただけなのに、「処理を開始します」「処理が完了しました」という出力が追加されていることがわかると思います。これがデコレータの機能になります。
処理を開始します
こんにちは!
処理が完了しました
デコレータの動き
上記のプログラムの解説です
my_decorator
は関数を受け取り、別の関数wrapper
を返す関数ですwrapper
関数では、元の関数func
を呼び出す前後に処理を追加しています@my_decorator
はsay_hello = my_decorator(say_hello)
と同じ意味でになります。つまり、say_hello
関数を引数としてmy_decorator
が呼び出される形にになります。
プログラムの動きは以下になります
say_hello
はmy_decorator(say_hello)
に書き換えられるmy_decorator
は、wrapper
関数を返す- 結果として
wrapper()
という呼び出しになり、「処理を開始します」「処理が完了しました」が追加される
デコレータの動作は上記のようになります。my_decorator
が関数wrapper
を返し、最終的にはwrapper()
となることでwrapper
関数が呼び出されることになります。
デコレータの構文
デコレータの基本構文
デコレータの基本構文は以下になります
@decorator_name
は、その下に書かれた関数の定義に適用される- デコレータは関数を受け取り、別の関数を返す必要がある
基本的には、上記の2つのルールだけですが、書き方自体はある程度テンプレート化しています。
引数と戻り値がある場合のデコレート
関数に引数や戻り値がある場合はwrapper
関数で受け取り、関数に引き渡します。
Pythonでは、*args, **kwargs
とすることで引数を全て受け取り、関数に渡すことができます。
以下のプログラムは、引数のある関数に対してデコレータを使う例です。
def my_decorator(func):
def wrapper(*args, **kwargs): # *args と **kwargs で引数を可変長に対応
print("処理を開始します")
result = func(*args, **kwargs)
print("処理が完了しました")
return result
return wrapper
@my_decorator
def add(a, b):
return a + b
print(add(2, 3)) # 出力:処理を開始します、5、処理が完了しました
*args
と **kwargs
の役割これらを使うことで、どんな引数を持つ関数でもデコレートできるようになります
*args
: 位置引数をタプルとして受け取る**kwargs
: キーワード引数を辞書として受け取る
なお、引数はデコレーターで追加、変更することも可能です。デコレータで引数を修正することで、関数の挙動を変化させることもできます。
例えば、以下のようにするとデコレータで引数を二乗して計算することが可能です。このプログラムではnew_args = [x * x for x in args]
として、引数を再計算してnew_args
を作成しています。
def my_decorator(func):
def wrapper(*args, **kwargs):
print("処理を開始します")
new_args = [x * x for x in args]
result = func(*new_args, **kwargs)
print("処理が完了しました")
return result
return wrapper
@my_decorator
def add(a, b):
return a + b
print(add(2, 3))
デコレータの使い方(応用)
時間計測
個人的によく使うデコレータで、関数の処理時間を計測するものになります。
プログラム中に書くと煩雑になりますが、@calc_time
を計測対象の関数定義の上に書くだけなので便利です。
import time
def calc_time(func):
def wrapper():
start = time.time()
result = func()
end = time.time()
print(f"処理時間は{end - start:.3f}秒でした")
return result
return wrapper
@calc_time
def loop():
for _ in range(100_000_000):
pass
loop()
デコレーターを2つ以上並べる例
デコレータを複数並べた場合は並べた順番で呼び出されます。例えば以下のプログラムでは、my_decorator → my_decorator2 → say_helloの順で呼び出されます。
def my_decorator(func):
def wrapper():
print("処理を開始します")
result = func()
print("処理が完了しました")
return result
return wrapper
def my_decorator2(func):
def wrapper():
print("START")
result = func()
print("END")
return result
return wrapper
@my_decorator
@my_decorator2
def say_hello():
print("こんにちは!")
say_hello()
プログラムの実行結果は以下になります。
処理を開始します
START
こんにちは!
END
処理が完了しました
デコレータ自身の引数を持たせる例
Pythonのデコレータ自身に引数を持たせることもできます。
基本的な構造は以下のようになります。
デコレータの記述
- 外側の関数(引数を受け取る)
デコレータの設定用の引数を受け取ります decorator
関数
元の関数を引数として受け取る関数ですwrapper
関数
元の関数をラップして処理を追加する
下記のプログラムでは、デコレータで指定した回数だけ呼び出しを繰り返します。@repeat(3)
となっているので、hello
は3回呼び出されます。
def repeat(n):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(n):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def hello():
print("Hello, world!")
hello()
Hello, world!
Hello, world!
Hello, world!
デコレータを使って関数を登録し、登録した関数を呼び出す例
デコレータを使って関数を登録し、登録した関数を呼び出す例です。Flaskなどのフレームワークで利用されていることがあるもので、結構便利ですので紹介しておきます。
下記のプログラムではsample
クラスはset
とcall
の2つの関数を持ちます。
@sample.set(n)
とすると、関数がnという番号で記録されます。
sample.call(n)
とすると、対応する関数が呼び出されます。
class Sample:
def __init__(self):
self.registry = {}
def set(self, n):
def decorator(func):
self.registry[n] = func # n番目の関数を登録
return func
return decorator
def call(self, n):
if n in self.registry:
return self.registry[n]() # 登録された関数を実行
else:
raise ValueError(f"Function {n} is not registered.")
# Sample のインスタンス作成
sample = Sample()
# 関数を登録
@sample.set(1)
def func1():
return "Function 1 executed"
@sample.set(2)
def func2():
return "Function 2 executed"
# 関数を呼び出し
print(sample.call(1)) # "Function 1 executed"
print(sample.call(2)) # "Function 2 executed"
デコレータがfunc = my_decorator(func)
だということを思い出すと、以下の記述は、
func1 = sample.set(1)(func)
という表記と同じになります。
@sample.set(1)
def func1():
return "Function 1 executed"
デコレーターを使うことで、複数の関数を登録し条件に合わせて呼び出す関数を選択するような処理を書くことができます。処理する関数を追加・削除したい場合に便利な記述です。

Flaskでは、特定のURLが呼び出された場合の処理を、デコレータを使って追加していきます。慣れないうちは、わかりにくかったですが、慣れると書きやすいです。
まとめ
以上、デコレータの使い方についてまとめてみました。デコレータは使い方次第では強力です。特に、コードを変更せずに処理を追加できるのが便利です。フレームワークなどでも使われていることがあるので、役割と原理を覚えておいて損はないと思います。