Python PR

【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

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

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

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のデコレーターについて解説しました。

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

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