【Python】デコレータについて

Python

はじめに

Pythonのデコレータについて解説します。

デコレータとは

デコレータとは関数を修飾するための仕組みです。
既存の関数に対して、関数の中身を変えることなく処理を追加することが出来ます。

しげっち
しげっち

[decorate]デコレート(飾る、装飾)が語源みたいだね。

にゃーすけ
にゃーすけ

デコレートよりチョコレートの方が好きだにゃー

デコレータの使い方

記述方法

関数定義の上側に@から始まるデコレート関数名を記述することで使用することが出来ます。

@デコレート関数
def 関数():
  #関数の処理
  ・・・・・・・・・・

しげっち
しげっち

そういえばクラスを作成する時に定義する、クラスメソッド(@classmethod)やスタティックメソッド(@staticmethod)もデコレータを使ってるね。

サンプル

次のサンプルは「こんにちは。」と返すだけの関数です。

def func_aisatsu():
    return 'こんにちは。'

print(func_aisatsu())

# 実行結果
# こんにちは。

この関数をデコレートして「こんにちは。今日も良い天気ですね。」と返すようにします。

def deco(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs) + '今日も良い天気ですね。'
    return wrapper

@deco
def func_aisatsu():
    return 'こんにちは。'

# 実行結果
# こんにちは。今日も良い天気ですね。

1行目にdecoという名前で関数を作成します。
引数にfuncをもたせます。
2行目にwrapperという名前で関数内関数を作成します。
引数に*args, **kwargsをもたせます。ここの説明は後述します。
3行目func(*args, **kwargs)の部分が、func_aisatsu()関数の処理タイミングになります。
「こんにちは。」という処理結果に「今日も良い天気ですね。」の文字を追加してreturnしています。
6行目に@decoを記述することでfunc_aisatsu()関数をデコレートしています。

にゃーすけ
にゃーすけ

デコレータを使用すると、関数の中身を変更することなく処理を追加することができるにゃー。

関数の処理時間を表示するサンプル

もう一つサンプルを見てみましょう。

import time,timeit, datetime
def deco_time(func):
    def wrapper(*args, **kwargs):
        print(datetime.datetime.now().strftime('開始:%H:%M:%S')) #処理を開始する前の時刻
     #デコレータ先の関数を実行して処理時間を取得する。
        result = timeit.timeit(lambda: func(*args, **kwargs), number=1) 
        print('経過時間:{}'.format(result))
        print(datetime.datetime.now().strftime('終了:%H:%M:%S')) #処理が終了した後の時刻
    return wrapper

@deco_time
def test_func():
    for x in range(3):
        #時間のかかる処理
        time.sleep(1)
 
test_func()

# 実行結果
# 開始:09:30:23
# 経過時間:3.009300909
# 終了:09:30:26

11行目にtest_funcという名前で時間のかかる処理を行います。
どのくらい処理に時間がかかっているのか調べたいので、デコレータで処理時間を確認できるようにしました。

しげっち
しげっち

他の関数の処理時間を調べたいときも、その関数に@から始まるデコレータ関数を1行追加するだけなので汎用的に使うことができるね。

引数を持つ関数をデコレートする

デコレートされる側の関数に引数を持っている場合の処理を見てみます。
具体的には下記赤文字の引数を渡す場合です。

@デコレート関数
def 関数(引数):
  #関数の処理
  ・・・・・・・・・・

サンプルを見てみましょう。

def deco_start_msg(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs) + 'はじめました。'
    return wrapper

@deco_start_msg
def func_msg(msg):
    return msg 

print(func_msg('冷やし中華'))

# 実行結果
# 冷やし中華はじめました。

先に作成した引数なしの場合と比べてデコレート関数は特別変化ありません。
引数があればwrapper関数の*argsもしくは**kwargsに値が入ります。
なければ*argsと**kwargsは空の状態になります。

*argsと**kwargs

にゃーすけ
にゃーすけ

引数が2つ以上の場合はどうなるにゃ?

引数の数を2つにしてみましょう。
また、確認のためにargsの型と値をprint関数で出力します。

def deco_start_msg(func):
    def wrapper(*args, **kwargs):
        print('argsのTypeは{}'.format(type(args)))
        print('argsの値は{}'.format(args))
        return func(*args, **kwargs) + 'はじめました。'
    return wrapper

@deco_start_msg
def func_msg2(msg, msg2):
    return msg + msg2

print(func_msg2('冷やし中華', 'ゴマダレ味'))

# 実行結果
# argsのTypeは<class 'tuple'>
# argsの値は('冷やし中華', 'ゴマダレ味')
# 冷やし中華ゴマダレ味はじめました。

この実行結果からargsはタプル型であり、2つの引数がargsに入っていることがわかります。

しげっち
しげっち

デコレートされる関数の引数がいくつなのか、デコレートする関数は気にする必要がないってことだね。

にゃーすけ
にゃーすけ

argsの頭についている*(アスタリスク)はなんだろにゃ?

引数名の頭にを付けると任意の数の引数を指定することが出来ます。
そしてその引数はタプルとして扱われるのです。
これを可変長引数といいます。

それでは**kwargsのようにが2つ付いているのはなんでしょうか?
実はこれも可変長引数になります。
*が2つある場合は辞書として扱われます。

  • *args   *が一つは引数がタプルとして扱われます。
  • **kwargs  *が二つは引数が辞書として扱われます。

なお、可変長引数はデコレータ関数以外でも使用できます。

デコレータに引数を渡す

次は、デコレート関数に引数を渡す方法を見ていきます。

@デコレート関数(引数)
def 関数():
  #関数の処理
  ・・・・・・・・・・

サンプルを見ていきましょう。

def deco_start_msg_price(price):
    def deco_start_msg(func):
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs) + 'はじめました。' + price
        return wrapper
    return deco_start_msg

@deco_start_msg_price('¥890')
def func_msg2(msg, msg2):
    return msg + msg2

print(func_msg2('冷やし中華', 'ゴマダレ味'))

# 実行結果
# 冷やし中華ゴマダレ味はじめました。¥890

これまでのデコレータ関数を更にネストして、1行目に新しく値段を引数で渡せる関数を追加しました。
4行目で、値段の引数を使用しています。

にゃーすけ
にゃーすけ

サンプルを見ただけでは分かりづらいにゃあ。

しげっち
しげっち

これは実際に入力をしてみれば処理の流れが分かりやすくなるよ。

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

デコレータを複数付けてみます。

@デコレート関数1()
@デコレート関数2()
def 関数():
  #関数の処理
  ・・・・・・・・・・

下のようなサンプルを作成してみました。

def add_deco(func):
    def wrapper(*args, **kwargs):
        print('■' * 40)
        func(*args, **kwargs)
        print('■' * 40)
    return wrapper

def deco_start_msg_price(price):
    def deco_start_msg(func):
        def wrapper(*args, **kwargs):
            print(func(*args, **kwargs) + 'はじめました。' + price)
        return wrapper
    return deco_start_msg

@add_deco
@deco_start_msg_price('¥890')
def func_msg2(msg, msg2):
    return msg + msg2

func_msg2('冷やし中華', 'ゴマダレ味')

# 実行結果
# ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
# 冷やし中華ゴマダレ味はじめました。¥890
# ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

1行目に新しくadd_decoというデコレータ関数を追加しました。
これは出力メッセージの上下に■を40個配置する関数です。
15、16行目にデコレータを付けています。
複数のデコレータがある場合、上から順に実行されます。
つまりサンプルではadd_deco関数から始まり、3行目の■が40個がprint出力されます。
次に4行目でfuncが呼ばれるので、ここで2つ目のデコレータ(deco_start_msg_price)が開始され、
11行目のメッセージがprint出力されます。
2つ目のデコレータ処理はこれだけなので、また1つ目のデコレータにもどり
5行目の■40個がprint出力されます。

しげっち
しげっち

複数デコレータが付いている場合は上から順に実行されることを覚えよう。

まとめ

Pythonのデコレータについて解説しました。

  • デコレータは関数の処理を変更することなく、処理を追加することができる。
  • 引数をもつ関数をデコレートした場合は可変長引数として、タプルか辞書の形で参照できる。
  • デコレータに引数を付けることも可能。
  • デコレータは複数つけることができ、上から順に実行する。

デコレータを使用しているライブラリとして、WEBアプリケーションフレームワークであるFlaskが挙げられます。

Flaskについては【Python】Flask入門を見てください。

しげっち
しげっち

最後まで読んでいただき、ありがとうございます。


タイトルとURLをコピーしました