【Python】デコレータ(Decorator)って何だろう?

■ 概要
デコレータは、前回の記事で取り上げたクロージャーの応用形です。
関数に機能を追加するための「関数」のことで、元の関数には手を加えずに、前後に処理を追加したり、ログを取ったりできます。
Pythonでは、@記法 を使って簡潔にデコレータを適用できます。
まずは、デコレータの前に高階関数を取り上げます。
■ 高階関数
高階関数とは?
高階関数は、「関数を引数に取る」または「関数を返す」関数のことを言います。
Pythonは関数もオブジェクトなので、変数と同様に渡したり返したりできます。
高階関数には、下記のいずれか、または両方の特徴を満たします。
条件 | 例 |
---|---|
関数を引数に取る | map() , filter() , sorted(..., key=...) |
関数を返す | クロージャー、デコレータなど |
高階関数の具体例1:関数を引数に取る関数
def shout(text):
return text.upper()
def whisper(text):
return text.lower()
def greet(func):
message = func("Hello!")
print(message)
greet(shout) # HELLO! → 大文字に
greet(whisper) # hello! → 小文字に
- 関数greet()は関数shoutやwhisperを引数として受け取っています
高階関数の具体例2:関数を返す関数(= クロージャー)
def make_multiplier(factor):
def multiplier(x):
return x * factor
return multiplier
times3 = make_multiplier(3)
times5 = make_multiplier(5)
print(times3(10)) # 30
print(times5(10)) # 50
- 関数make_multiplierは、関数multiplierを返しています。
- times3、times5はそれぞれ異なる「関数オブジェクト」です。
高階関数の具体例3:組み込み関数
関数名 | 説明 |
---|---|
map(func, iterable) |
各要素に func を適用 |
filter(func, iterable) |
条件を満たす要素だけを返す |
sorted(iterable, key=func) |
ソート基準に func を使う |
functools.partial |
一部の引数を固定した関数を返す |
高階関数のまとめ
用語 | 説明 |
---|---|
高階関数 | 関数を引数にしたり、返したりする関数 |
メリット | 関数を組み合わせて柔軟で再利用性の高いコードが書ける |
関連概念 | クロージャー、デコレータ、ラムダ式、関数型プログラミング |
■ デコレーター記法を使わずに機能追加
具体例:高階関数(クロージャー)
def greet():
print("こんにちは!")
def wrapper(func):
def inner():
print("=== 前処理 ===")
func()
print("=== 後処理 ===")
return inner
wrapped_greet = wrapper(greet)
wrapped_greet()
実行結果
=== 前処理 ===
こんにちは!
=== 後処理 ===
- greet()の前後に処理を挟み込んで機能追加しています。
- 元々の関数greet()の内部には手が加えられていません。
- innner()はgreet関数をラップ(包み込む)しています。
これがデコレータです。
■ デコレーター記法を使った機能追加
具体例:デコレータ記法
def wrapper(func):
def inner():
print("=== 前処理 ===")
func()
print("=== 後処理 ===")
return inner
@wrapper
def greet():
print("こんにちは!")
greet()
実行結果
=== 前処理 ===
こんにちは!
=== 後処理 ===
- 高階関数(クロージャー)と同じ結果となります。
- @wrapper は
greet = wrapper(greet)
と同じ意味です。
このように、@処理を追加する高階関数名
を指定します。
■ 引数を伴うデコレータ
これまでのデコレータは、引数なし、戻り値なしの関数でしか使用できません。
引数があると
TypeError: wrapper.<locals>.inner() takes 0 positional arguments but 2 were given
のようなエラーが発生します。
そこで、次のように可変長引数と戻り値を設定することで、任意の関数でデコレータを利用することが可能になります。
具体例
def wrapper(func):
def inner(*args, **kwargs): # 可変長引数を設定
print("前処理")
result = func(*args, **kwargs) # 戻り値resultの設定
print("inner内:" + str(result))
print("後処理")
return result
return inner
@wrapper
def add(a, b):
return a + b
print(add(3, 4))
実行結果
前処理
inner内:7
後処理
7
■ デコレータを使った簡単なログ実装例
実装例
def log(func):
def inner(*args, **kwargs):
print(f"呼び出し: {func.__name__} with {args} {kwargs}")
return func(*args, **kwargs)
return inner
@log
def say_hello(name):
print(f"こんにちは、{name}さん!")
say_hello("Sato")
実行結果
呼び出し: say_hello with ('Sato',) {}
こんにちは、Satoさん!
おつかれさまです。
次回はラムダ式あたりをご紹介できたらと思っています。