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

【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さん!

おつかれさまです。
次回はラムダ式あたりをご紹介できたらと思っています。