Pythonにはコードを書き換えずに、関数の処理に変更を加えたいようなことがあります。これはデコレータを使うことで可能になります。デコレータは関数を入力として持ち、別の関数を返す関数です。
関数内関数に似たようなものですが、ちょっとPython初心者にはややこしい感じがするかもしれません。このようなものがあるということを今の時点では頭に入れておきましょう。
デコレータの作り方・書き方
デコレータは、*argsと**kwargs、関数内関数、引数としての関数を持ちます。
コードで見た方が早いと思うので、コードを書いていきましょう。
まず、次のような関数のコードを用意します。
1 2 3 4 5 |
def add_calc(a, b): return a + b r = add_calc(10, 100) print(r) |
関数の引数に値を入れて足し算させるコードですが、ここはわかりますよね。
実行するとこうなります。
10と100が足し算されて、110の値が出力されています。
このadd_calc(10, 100)を実行する時に何か別の処理を行いたいとします。
ここでは、関数を呼び出す前後にメッセージを出力することにします。次のようにコードを加えてみます。
1 2 3 4 5 6 7 8 |
def add_calc(a, b): return a + b print("計算を開始します。") r = add_calc(10, 100) print("計算が終了しました。結果を表示します。") print(r) |
計算時にメッセージを表示するprint文を加えただけですね。
Atomで実行するとこうなります。ファイル名はdecorator.pyとしています。
元の関数の処理に、print文を付け加えた形になっています。
デコレータのコードを書く
このような関数の処理に何か付け加えたい時に使えるのがデコレータです。このコードを、デコレータを使って書き換えていきましょう。
1 2 3 4 5 6 7 8 9 10 |
def print_passage(func): def passage(*args, **kwargs): print("計算を開始します。") result = func(*args, **kwargs) print("計算が終了しました。結果を表示します。") return result return passage def add_calc(a, b): return a + b |
最初にも書きましたが、デコレータは、*argsと**kwargs、関数内関数、引数としての関数を持ちます。
すでに定義しているadd_calc()の上側に、新たにprint_passage()という関数を定義しています。この関数は、関数funcを引数に持つ関数内関数となっています。
関数内にpassage(*args, **kwargs)という関数を定義して、引数である関数を実行してその結果をresultで返し、この関数内の結果をpassage()として返すのではなく、passsageのオブジェクトとして返していることに注意です。
これを、上でやった計算と同じように引数を入れて実行するには、次のように書きます。
1 2 3 |
f = print_passage(add_calc) r = f(10, 100) print(r) |
print_passage(add_calc)と引数に計算する関数を入れてオブジェクト化します。これを変数fに入れ、丸括弧をつけて値を入れることで実行されます。その値を変数rに代入し、printで出力しているわけです。
このコードを全体で実行すると次のようになります。
先ほどやったことと同じように、関数に処理を加えることができました。
デコレータはこう言った処理を元の関数を書き換えずに行うことができるのですが、この方法だと最後の呼び出し実行時にデコレータだと理解して書かないといけないので、複雑でわかりにくい面があります。
デコレータの書き方
このわかりにくさを避けたいので、デコレータは@を使って次のように書くことができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def print_passage(func): def passage(*args, **kwargs): print("計算を開始します。") result = func(*args, **kwargs) print("計算が終了しました。結果を表示します。") return result return passage @print_passage def add_calc(a, b): return a + b r = add_calc(10, 100) print(r) |
違いがわかるでしょうか。
デコレータは、元の関数の上に@をつけて処理を加えたい関数名を記述すればいいだけです。実行する時も元の計算したい関数に引数を入れて出力しているだけなので、わかりやすいと思います。
実際に実行すると、こうなります。
同じ結果が得られています。
コードのわかりにくさ、複雑さの違いを確認しておきましょう。
デコレータの使い方
デコレータの便利なところは、一度デコレータのコードを書いておけば、違う関数を使う時に同様にデコレータを使うことができる点ですね。
デコレータを何度も使う
1 2 3 4 5 6 |
@print_passage def multiplication_calc(a, b): return a * b r2 = multiplication_calc(10, 100) print(r2) |
このように、2つの引数を掛け算する関数を定義して、デコレータを同様に使っています。
上の足し算のコードと合わせて実行するとこうなります。
このようにデコレータは一度書いてしまえば、何度も使えるということがわかりますね。
2つのデコレータを使う
今度は、デコレータを複数作って使う場合について見ていこうと思います。ここでは2つのデコレータを用意することにします。先ほどのコードに、もう1つ次のデコレータのコードを加えてみましょう。
1 2 3 4 5 6 7 8 9 |
def print_info(func): def info(*args, **kwargs): print("関数の情報を表示します。") print("関数名:", func.__name__) print("引数の値:", args) print("キーワード:", kwargs) result = func(*args, **kwargs) return result return info |
関数の情報を表示する処理を行っています。func.__name__ というものが見慣れ無いと思いますが、引数で渡した関数の名前を出力するということだけ今は理解しておきましょう。引数の値と、ここでは使っていませんがキーワードも表示するようにしています。あとは計算結果ですね。
これを実行すると、次のようになります。
それぞれ情報が表示されているのがわかります。(見やすくするために、print_passage関数のデコレータの表示を閉じています。)
これで2つのデコレータの用意ができました。
複数のデコレータを使うには、デコレータを重ねることで使うことができます。2つ用意したデコレータを一緒に使ってみましょう。
ただし、今回の場合は使い方が2通りあります。どちらのデコレータを上にするかで挙動が違います。
1 2 3 4 5 6 7 |
@print_info @print_passage def add_calc(a, b): return a + b r = add_calc(10, 100) print(r) |
まずはこちらのデコレータの使い方です。実行するとこうなります。
次はこちらのデコレータの使い方です。
1 2 3 4 5 6 7 |
@print_passage @print_info def add_calc(a, b): return a + b r = add_calc(10, 100) print(r) |
デコレータを先ほどとは順序を逆にしています。実行するとこうなります。
この2つの結果を比べて見ると、処理の流れが違っています。
このように、複数のデコレータを利用する場合は、その使い方で挙動が違うと言うことを理解して使う必要があります。
まとめ
Pythonの関数を使う時に、コードを変更することなく別の処理を加えたい場合は、デコレータを使います。
デコレータは*argsと**kwargs、関数内関数、引数としての関数を持ちます。これを変更を加えたい関数を引数にして実行すれば良いのですが、コードが複雑になるので、関数の上に@を使ってデコレータを置くことで処理がわかりやすくなります。
デコレータは一度定義すれば、あらたな関数が出てくるたびに何度も利用することができます。
複数のデコレータは重ねて使うことができますが、重ねる順番によって挙動が変わることも頭に入れて使いましょう。
デコレータもPython入門レベルでは少し難しいところでもあるので、こういったものがあるということだけでも最初は頭に入れておきましょう。