PR
更新:2024/10/17

Pythonのデコレーターの使い方・例題について

ふゅか
ふゅか
デコレーターって、関数に処理を追加できるすごく便利な機能なんだよね♪
はるか
はるか
関数の実行時間を計測したいときとかに使えそう。

1. デコレーターとは

デコレーターは、関数やメソッドに機能を追加するための仕組みです。デコレーターは、関数の性質を利用して、ある関数の前後に処理を追加したり、関数を改良したりするのに便利な方法です。

2. 基本的な使い方

デコレーターは、関数を引数に取り、別の関数を返す関数です。

2.1. 基本的なデコレーターの例

まずは、シンプルなデコレーターの例を見てみましょう。

def my_decorator(func):
    def wrapper():
        print("関数が呼び出される前に実行されます")
        func()  # 引数の関数を実行
        print("関数が呼び出された後に実行されます")
    return wrapper

my_decorator は、元の関数 func を受け取り、その前後に処理を追加している wrapper 関数を返しています。これを元の関数に適用してみます。

@my_decorator
def say_hello():
    print("こんにちは!")

say_hello()

2.1.1. 実行結果

関数が呼び出される前に実行されます
こんにちは!
関数が呼び出された後に実行されます

@my_decorator の部分が、デコレーターの適用です。これは say_hello 関数を my_decorator に渡し、wrapper 関数に置き換えることを意味しています。

2.1.2. デコレーターの仕組み

デコレーターがどう動いているのかを順番に説明します。

  1. 元の関数 (say_hello) が my_decorator に渡されます。
  2. my_decorator は、wrapper 関数を返します。
  3. @my_decorator によって、 wrapper 関数が実行されます。
  4. wrapper 関数が実行されると、元の say_hello 関数の前後にメッセージが表示され、さらに say_hello 関数自体も実行されます。
はるか
はるか
基本的な使い方はわかった。関数を引数に取って、内部で定義した関数を返すだけか。
ふゅか
ふゅか
そうそう!「関数が呼び出される前に実行されます」と「後に実行されます」って、デコレーターで一つの関数に色々な処理を追加できちゃうんだ~。

2.2. 引数を持つ関数にデコレーターを使う

引数を持つ関数にデコレーターを適用する場合、wrapper 関数にも引数を受け取れるようにする必要があります。

def my_decorator(func):
    def wrapper(name):  # 任意の引数を受け取る
        print("関数が呼び出される前に実行されます")
        result = func(name)  # 引数を渡して関数を実行
        print("関数が呼び出された後に実行されます")
        return result
    return wrapper

@my_decorator
def say_hello(name):
    print(f"こんにちは、{name}!")

say_hello("ふゅか")

2.2.1. 実行結果

関数が呼び出される前に実行されます
こんにちは、ふゅか!
関数が呼び出された後に実行されます

2.3. 可変長引数を使う

先ほど、wrapper関数にnameの変数を渡していましたが、任意の引数(可変長引数)にしてもプログラムは動きます。

def my_decorator(func):
    def wrapper(*args, **kwargs):  # 任意の引数を受け取る
        print("関数が呼び出される前に実行されます")
        result = func(*args, **kwargs)  # 引数を渡して関数を実行
        print("関数が呼び出された後に実行されます")
        return result
    return wrapper

@my_decorator
def say_hello(name):
    print(f"こんにちは、{name}!")

say_hello("ふゅか")
ふゅか
ふゅか
引数を持つ関数にデコレーターを適用する場合も簡単だよ![*args, **kwargs] で任意の引数を受け取れるようにするだけ♪
はるか
はるか
引数が渡されても、デコレーターの構造自体は変わらない。

2.4. 例:関数の実行時間を測るデコレーター

次に、デコレーターの例として、関数の実行時間を測定するデコレーターを作ってみます。

import time

def time_decorator(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()  # 開始時間を記録
        result = func(*args, **kwargs)
        end_time = time.time()    # 終了時間を記録
        print(f"関数 {func.__name__} の実行時間: {end_time - start_time} 秒")
        return result
    return wrapper

@time_decorator
def slow_function():
    time.sleep(2)  # 2秒待機
    print("関数が実行されました")

slow_function()

実行結果は次のようになります。

このデコレーターは、関数の前後で時間を記録し、実行時間を表示しています。

3. デコレーターの例題

3.1. 例題1: 砂の量を増やすデコレーター

砂漠での一日で、砂の量が2倍に増えることをシミュレートするデコレーターを作成します。砂の量を表す関数にデコレーターを適用して、2倍に増える計算を行いなさい。

期待される出力:
200

解答の手順:

  1. sand_doubleというデコレーターを定義し、引数として与えられた量を2倍にする処理を記述します。
  2. sand_in_desert関数にデコレーターを適用し、砂の量を2倍にする機能を付与します。
  3. 初期の砂の量(100)を与えた際に、デコレーターがそれを2倍にして出力します。

解答:

def sand_double(func):
    def wrapper(amount):
        return func(amount) * 2
    return wrapper

@sand_double
def sand_in_desert(amount):
    return amount

print(sand_in_desert(100))

3.2. 例題2: 砂の耐久テストデコレーター

砂の山に何度も人が歩くことを想定し、デコレーターで砂の山の耐久度を試します。もし砂の山の耐久度が0以下になった場合、崩壊するメッセージを出力します。

解答の手順:
  1. sand_durabilityというデコレーターを作成し、耐久度が歩数で減少するロジックを組みます。
  2. durability - stepsが0以下になると、崩壊のメッセージを返す処理を追加します。
  3. walk_on_sand関数で耐久度を管理し、デコレーターを適用して、耐久度を減らす機能を持たせます。

解答:

def sand_durability(func):
    def wrapper(durability, steps):
        if durability - steps <= 0:
            return "砂の山は崩壊しました!"
        return func(durability, steps)
    return wrapper

@sand_durability
def walk_on_sand(durability, steps):
    return f"残りの砂の耐久度: {durability - steps}"

print(walk_on_sand(10, 5))
print(walk_on_sand(10, 15))

3.3. 例題3: 砂時計デコレーター

砂時計の時間を計測するため、デコレーターを使って関数が完了するまでにかかる時間を測定します。すべての砂が落ちるのに2秒かかるとします。

期待される出力:
処理時間: 2.0秒
砂が落ちました

解答の手順:

  1. sand_timerデコレーターを作成し、開始時間と終了時間を測定する処理を追加します。
  2. sand_fall関数は砂が落ちる処理(2秒間の待機)を再現します。
  3. デコレーターを適用して、処理時間を出力します。

解答:

import time

def sand_timer(func):
    def wrapper():
        start_time = time.time()
        result = func()
        end_time = time.time()
        return f"処理時間: {end_time - start_time}秒\\\\n{result}"
    return wrapper

@sand_timer
def sand_fall():
    time.sleep(2)  # 砂が落ちるのに2秒かかる
    return "砂が落ちました"

print(sand_fall())


check

Pythonの基本から応用まで、幅広くカバーする記事を公開中です。学習のポイントや実践的なコード例を通じて、Pythonの魅力と実用性を深く理解することができます。ぜひ、こちらの記事で気になる記事を見つけてください!

Pythonの記事のまとめ

PR