Phython

【Python】デコレーターとは?
使い方をわかりやすく解説!

本記事では、pythonにおけるデコレーターの使い方について説明していこうと思います。

デコレーターは、名前の通り関数の装飾を行う上で非常に便利な機能を持っているので、ぜひ本記事を通して理解を深めていただければと思います。

ちなみに、本ブログでは初心者の方でも理解できるように丁寧に説明しているので、全くの初心者という方も安心してください。

また、pythonをまだインストールしていないといった方は以下の記事を参考にしてインストールしてみましょう。

この記事でわかること

  • デコレーターの使い方
  • デコレーターを簡単に導入する方法
  • 複数のデコレーターを同時に使用する方法

 デコレーターについて

では、まずデコレーターとはどのようなものなのかについて説明していこうと思います。
また、デコレーターのコードを理解するうえでクロージャ―の知識が必要となってくるので、クロージャ―についてよくわからないといった方は以下の記事を参考にしてみて下さい。

デコレーターとは?

デコレーターとは、ある関数に対して、コードの中身を変更せずに処理を追加したり変更したりするもののことです。

例えば、以下のような関数があったとします。

<input>

def test():
    print("This is test.")

test()

<output>

'This is test.'

『This is test』といったただの文字列を表示するような簡単な関数を作成しました。
デコレーターを使うと、関数のコードの中身を変えずに処理を追加することが出来るので、例えば以下のようなことが可能です。

<input>

デコレーションに関する処理記述(後ほどやります)


def test():
    print("This is test.")


デコレーションに関する処理記述

<output>

start
This is test.
finish

先ほどの例と比べると、『This is test』という関数の出力の前後に新しい文字列が追加されていますよね。
これがデコレーションです。
実際に自分でデコレーションの処理を書かなければならないので、もう少しコードは複雑になるのですが、とりあえずデコレーションとは、『関数内のコードを変えずに関数に処理を追加するもの』というイメージを持っておいてください。

デコレーターの使い方①

では、実際にコードを用いてデコレーターの作成方法や使い方について確認していきましょう。

<input>

# デコレーター作成
def start_finish(func):
    def add_start_finish():
        print("decoration is start.")
        func()
        print("decoration is finish.")
    return add_start_finish

# 元の関数
def test():
    print("This is test.")

# デコレーションを加えた関数を出力
f = start_finish(test)
f()

<output>

decoration is start.
This is test.
decoration is finish.

上記のコードでは、『test』という関数に『start_finish』というデコレーターを用いて機能の追加を行っています。

まずは、デコレーターについての説明です。
デコレーター『start_finish』では、関数を引数として扱います。

デコレーターの関数内では、どのような処理が行われているかというと、まず最初に『decoration is start』と出力してその後に引数funcを実行し、最後に『decoration is finish』と出力する関数内関数『add_start_finish』を定義しています。
その後、『return add_start_finish』によって、関数内関数の実行手前の状態を戻り値として返します。

また、『デコレーションを加えた関数を出力』といった部分では、変数fに『start_finish(test)』を代入してそれをf()によって実行することで、引数を関数『test』にした『start_finish』が実行されます。

これにより、元の関数の前後にデコレーションが追加されたということになります。

デコレーターの使い方②

では、先ほどまででデコレーターの基本的な使い方について説明したのですが、実はもう少し簡単にデコレーターを導入することが出来るのでご紹介していこうと思います。

<input>

# デコレーター作成
def start_finish(func):
    def add_start_finish():
        print("decoration is start.")
        func()
        print("decoration is finish.")
    return add_start_finish

# 元の関数
@start_finish
def test():
    print("This is test.")

# デコレーションを加えた関数を出力
test()

<output>

decoration is start.
This is test.
decoration is finish.

デコレーターの作成方法は先ほどまでと変わらないのですが、変わったのはその下の部分です。

元の関数の部分に『@start_finish』というコードが追加されていますよね。
このコードは、関数『test』をデコレーター『start_finish』によってデコレーションするよといった命令になっています。
なので、この一行によってtestがデコレーターによって処理されて、最終的に『test()』といったように関数『test』をただ実行するだけでデコレーターに処理された後の『test』を得ることが出来ます。

要するに、以下のようなコードを書けば簡単にデコレーターを使用することが出来るのです。

デコレーターの処理

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

関数名()

基本的にはこちらを使うことが多いので、先ほどの①の方のコードを理解しつつこちらのコードも使えるようにしましょう。

 デコレーターの様々な使い方

デコレーターの基本的な使い方を確認したところで、続いてはそんなデコレーターの様々な使い方について確認していこうと思います。

引数を持つ関数へのデコレーター

デコレーターはどのような関数に対しても適用することが出来るので、もちろん引数を持つような関数に対しても適用可能です。
ここでは、例として以下のような関数にデコレーターを適用するとしましょう。

<input>

# 関数宣言
def test2(string):
    print("This is test.", string)

# 関数呼び出し
test2("This is an additional comment.")

<output>

This is test. This is an additional comment.

引数が『This is test』の後ろに表示されるようないたってシンプルな関数です。
では、こちらを先ほどのデコレーターを用いてデコレーションしていきましょう。

<input>

# デコレーター作成
def start_finish(func):
    def add_start_finish(string):
        print("decoration is start.")
        func(string)
        print("decoration is finish.")
    return add_start_finish

# 元の関数
@start_finish
def test2(string):
    print("This is test.", string)

#デコレーションを加えた関数を出力
test2("This is an additional comment.")

<output>

decoration is start.
This is test. This is an additional comment.
decoration is finish.

今回は元の関数に引数があるので、関数内関数『add_start_finish』内で実行している『func()』の方にも引数を渡してあげないといけませんよね。
そのために、関数内関数『add_start_finish』にも同じ引数を与えてあげます。
デコレーターに加える変化はそれくらいですかね。

後は、もちろん最後に『test2』を実行する際には引数を代入して実行しましょう。
適切な結果が返ってくるはずです。

戻り値を持つ関数へのデコレーター

では、続いて戻り値を持つような関数へのデコレーター適用方法を考えていきます。
先ほどと同様に、例として使用する関数を以下に示しますね。

<input>

# 関数宣言
def test3(num1, num2):
    return num1 + num2

# 関数呼び出し
answer = test3(10, 20)
print(answer)

2つの数字を引数として、足し算した結果を戻り値とするような関数を作成しました。
では、こちらの関数にも、以下でデコレーターを適用してみます。

<input>

# デコレーター作成
def start_finish(func):
    def add_start_finish(a, b):
        print("decoration is start.")
        func(a, b)
        print("decoration is finish.")
        return a + b
    return add_start_finish

# 元の関数
@start_finish
def test3(num1, num2):
    return num1 + num2

# デコレーションを加えた関数を出力
answer = test3(10, 20)
print(answer)

<output>

decoration is start.
decoration is finish.
30

今回は元の関数が二つの引数と戻り値を持つので、デコレーターの関数内関数『add_start_finish』も同様に二つの引数と戻り値を持つ必要があります。
そのため、引数には『a, b』戻り値には『a + b』を設定します。
こうすることで、適切な結果が返ってきました。

ちなみに、この時戻り値は一番下の行に出力される形になってしまうのですが、戻り値はreturn文によって生成されているため、仕方がないです。
もしそれを避けたいのであれば、以下のようなコードを書くしかありません。

<input>

# デコレーター作成
def start_finish(func):
    def add_start_finish(a, b):
        print("decoration is start.")
        func(a, b)
        print("decoration is finish.")
    return add_start_finish

# 元の関数
@start_finish
def test3(num1, num2):
    print(num1 + num2)

# デコレーションを加えた関数を出力
test3(10, 20)

<output>

decoration is start.
30
decoration is finish.

このように、戻り値ではなくprint文によって足し算の結果を表示するような関数を作成すれば、関数内関数の『func(a, b)』の部分で結果の出力が行われることになるので、『decoration is start.』と『decoration is finish』の間に計算結果を入れることが出来ます。

複数のデコレーターを同時に使用

最後に、複数のデコレーターを一つの関数に対して同時に適用する方法についてです。
今回は二つのデコレーターを用いて一つの関数をデコレーションしていこうと思うのですが、その前に使用するデコレーターの紹介をしておきます。

# startとfinishを出力するデコレーター
def start_finish(func):
    def add_start_finish(a, b):
        print("decoration is start.")
        func(a, b)
        print("decoration is finish.")
        return a + b
    return add_start_finish

一つ目は、今回ずっと使用しているこれです。
『start』を出力→引数となる関数を実行→『finish』を出力→戻り値を返す
といった感じの操作を行うデコレーターですね。

# funcの情報を出力するデコレーター
def func_info(func):
    def add_func_info(a, b):
        print("func:", func.__name__)
        print("args:", a, b)
        result = func(a, b)
        print("result:", result)
        return a + b
    return add_func_info

二つ目はこちらのデコレーターです。
func.__name__にて関数の名前を出力→引数を表示→関数を実行した結果をresultに代入してresultを出力
といった操作を行います。

では、上記二つのデコレーターを同時に関数に適用していきましょう。

<input>

# startとfinishを出力するデコレーター
def start_finish(func):
    def add_start_finish(a, b):
        print("decoration is start.")
        func(a, b)
        print("decoration is finish.")
        return a + b
    return add_start_finish

# funcの情報を出力するデコレーター
def func_info(func):
    def add_func_info(a, b):
        print("func:", func.__name__)
        print("args:", a, b)
        result = func(a, b)
        print("result:", result)
    return add_func_info

# 元の関数
@start_finish
@func_info
def test3(num1, num2):
    return num1 + num2

# デコレーションを加えた関数を出力
answer = test3(10, 20)
print(answer)

<output>

decoration is start.
func: test3
args: 10 20
result: 30
decoration is finish.
30

それでは簡単にコードを説明していきますね。
デコレーターの中身は先ほど少しご紹介したので『#デコレーションを加えた関数を出力』の部分を見てみましょう。

まず一行目に『answer = test3(10, 20)』とあるので、関数testを実行していきます。
しかし、関数testはまず『start_finish』によってデコレーションされていることがわかるので、start_finishの方へ飛びましょう。

では、デコレーターを関数testを引数として実行していきます。
初めの『print("decoration is start.")』は今まで通り文字列を出力するような命令なので、出力結果の一行目に『decoration is start』といった文字列が表示されていますよね。
この後、デコレーターstart_finishでは、『func(a, b)』といったように関数が実行されます。
今回、funcにはtestを代入しているので、デコレーターの関数内関数でtestが呼び出されたといった感じですね。

したがって、『decoration is start』を出力した後、『#元の関数』の方に戻って『@func_info』に対する処理を行っていきます。
そのため、出力結果の2行目~4行目は二つ目のデコレーターにおける処理が行われているのですね。

二つ目のデコレーターにおける処理が終了したら、先ほどの関数『add_start_finish』内の『func(a, b』という処理が終了した形になるので、最後に『decoration is finish』といった文字列を出力して、『return a+b』によって計算結果を出力してプロうグラムは終了です。

 まとめ

今回は、デコレーターについていろいろな関数に対する適用方法をご紹介してきましたがいかがだったでしょうか。

最後の方のコードはとても複雑で、今回始めてデコレーターを学ぶ方にとっては理解に苦しむような内容になっていたと思いますが、心配する必要はありません。
自分でコードを組んでみて徐々に理解を深めていきましょう。

本記事の内容が少しでも皆様の役に立てれば幸いです。

-Phython
-,