Python

【Python】デコレーターの定義方法と使い方を解説

この記事では、Pythonのデコレーターの使い方を解説します。デコレーターとは、関数をラップするための機能です。関数にデコレーターを付けることで、関数を実行した際に、デコレーター定義関数に実行した関数を引数として渡し、デコレーター定義関数を実行します。それでは、デコレーターの使い方を見ていきましょう!

デコレーターの使い方

デコレーターは、以下のように使用します。

STEP1. デコレーター定義関数の定義

デコレーター定義関数は、以下のように関数をネストして定義します。内側の関数のwrapperという名前には構文的な意味はありませんがよく使用されます。

def デコレーター名(func):
    def wrapper(*args, **kw):
        # 処理
    return wrapper
func引数 デコレーターを付与した関数の参照が渡される
*args引数, **kw引数 デコレーターを付与した関数の引数が渡される

STEP2. 修飾する

作成したデコレータは、以下のように関数に修飾します。

@デコレーター名
def 何らかの関数():
    # 処理

サンプル

試しに、簡単なデコレーターを見てみましょう。以下のデコレーターは、関数を実行する前後に文字を出力するだけです。

# デコレーター定義関数
def decorator(func):
    def wrapper(*args, **kw):
        print('デコレーター 開始')
        # 受け取った関数の参照に引数を渡して実行
        func(*args, **kw)
        print('デコレーター 終了')
    return wrapper

# func関数にデコレータを付与
@decorator
def func():
    print('func関数 実行')

# func関数実行
func()

実行結果

デコレーター 開始
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():
    print('func1')

def func2():
    print('func2')

# デコレーター有り
func1()

# デコレーター無し
deco(func2)()

実行結果

>> func1の実行結果
deco - start
func1
deco - end

>> func2の実行結果
deco - start
func2
deco - end

まぎらわしく見えますが、deco関数を実行するとwrapper関数の参照を返しているだけです。そして、返されたwrapper関数の参照を()で実行しています。

docstringが参照できない?

実は、先ほどのデコレーター定義関数を使うと、付与した関数のドキュメンテーション文字列が参照不能になってしまう問題があります。

# デコレーター定義関数
def decorator(func):
    def wrapper(*args, **kw):
        pass
    return wrapper

# ドキュメンテーション文字列が定義された関数にデコレーターを付与
@decorator
def func():
    """ドキュメンテーション文字列"""
    pass

# func関数のドキュメンテーション文字列を参照
print(func.__doc__)

実行結果

None

そんな時は、functoolsモジュールのwraps()をwrapper関数に付与します。

from functools import wraps

def decorator(func):
    @wraps(func)                # wrapperに付与
    def wrapper(*args, **kw):
        pass
    return wrapper

@decorator
def func():
    """ドキュメンテーション文字列"""
    pass

print(func.__doc__)

実行結果

ドキュメンテーション文字列

これでラップした関数のドキュメンテーション文字列を参照することができます。

基本的に、wraps()はあった方が良い

引数を使用するデコレーター

引数が定義されている関数にデコレーターを付与させることもできます。いろいろな引数を定義した関数に、デコレーターを付けて動作を確認してみます。

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}

func(*args, **kw)によって、全て?の引数を正常に引き渡すことができます。

【Python】配列の要素を展開して変数に代入する方法を解説【アンパック】この記事では、Pythonのアンパックについて解説します。アンパックとは、配列の要素を展開し、変数に代入することを言います。また、関数の引数としてリストや辞書を展開して渡すこともでき、とても便利です。それでは、アンパックの使い方を見ていきましょう!...

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

戻り値を受け取りたい場合は、wrapper関数でreturnすれば良い。

def decorator(func):
    def wrapper(*args, **kw):
        print(func.__name__ + '関数 実行')
        return func(*args, **kw)            # 戻り値を返す
    return wrapper


@decorator
def add(x, y):
    return x + y

print(add(1, 2))

実行結果

add関数 実行
3

デコレーター自体に引数を定義する

デコレーターに引数を定義するには、さらに1つ関数を重ねます。

def デコレーター名(仮引数):
    def _デコレータ名(func):
        def wrapper(*args, **kw):
            # なんらかの処理
        return wrapper
    return _デコレータ名

引数は、関数に付与する際に渡します。

@デコレーター名(実引数)
def 関数():
    # なんらかの処理

試しに、デコレーターから引数を受け取り出力してみます。

def deco(num):
    def _deco(func):
        def wrapper(*args, **kw):
            print(num)              # 受け取った引数を出力
        return wrapper
    return _deco


@deco(1982)
def func():
    pass

func()

実行結果

1982

見た目がゴチャつくので難しく感じますが、やっていることは単純です。

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

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

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 start')
        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(deco2(test))()

うん、めちゃくちゃわかりずらい❗️

デコレーターを使うことで、かなりシンプルに表現できているのがわかります。

まとめ

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

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

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

最短3か月でエンジニア転職『DMM WEBCAMP COMMIT』