Python

【Python】デコレーターの使い方

この記事では、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関数が処理されているのがわかります。

それでは、デコレーターの使い方を見ていきましょう!

デコレーターの使い方

デコレータは以下の手順で使用します。

  1. デコレーター定義関数を作成
  2. 関数にデコレーターを修飾

デコレーター定義関数

デコレーター定義関数は以下のように定義します。
※ 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])

【Python】アンパックの使い方この記事では、Pythonのアンパックについて解説します。 アンパックを使うことでまとまったデータをバラバラに分解してそれぞれ変数...

戻り値を返すデコレーター

戻り値が定義されている関数のデコレーターの定義方法を見ていきます。

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を引数として渡し、その実行結果を返しています。

デコレーターを複数付ける

デコレーターは1つの関数に複数つけることもできます。

以下のコードは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))()

うん、めちゃくちゃわかりずらい!!
デコレーターを使うことでシンプルに記述できているのがわかります。

まとめ

この記事ではPythonのデコレーターについて解説しました。

デコレーターを使うことで簡単に関数をラップすることができます。あまり使う機会は多くないかもしれませんが便利な機能なので覚えておきましょう!

それでは今回の内容はここまでです。ではまたどこかで〜( ・∀・)ノ

参考decorator

最短3か月でエンジニア転職「DMM WEBCAMP COMMIT」
なんと転職成功率98%!
今なら3日以内のカウンセリング枠を予約&参加で「1,000円分のAmazonギフト券」をプレゼント!