▶ Pythonで自作関数を作る方法は以下の記事を参考にしてください
はじめに
今回はデコレータを扱います。デコレータは、「ある関数に」「その関数自体は変更せずに」「機能を加える」ことができる仕組みです。元の関数の内部に手を加えずに機能を追加・変更できるという点が大きなメリットとなります。まずは高階関数の確認をおこなってから、順に説明をしていきます。
高階関数
デコレータを扱う前に、まずは高階関数の確認をしておきましょう。関数もオブジェクトでしたね。なので引数や返り値に使うこともできます。高階関数は、関数を引数や返り値として扱っている関数のことです。
▶ 関数を引数や返り値として扱う例としてクロージャの解説記事も参考にどうぞ
関数の中身を変えずに、関数に機能を追加する例を見てみましょう。まずはデコレータを利用せずに考えてみましょう。次の関数を定義してください。
# 関数:introduce
def introduce(func):
def inner(name):
func(name)
print('Nice to meet you.')
return inner
# 関数:say_name
def say_name(name):
print(f"I'm {name}.")
1つ目の関数introduceは、関数を引数に取ります。この関数を呼び出すと、内部でinnerという関数が定義されてこの関数が返り値になります。このinner関数はnameという引数をとり、introduce呼び出し時に引数として渡した関数を実行後、print文を実行する関数となります。2つ目の関数say_nameは返り値はなく、ただ「Hello」とprintする関数です。
実際にやってみましょう。
intro=introduce(say_name)
intro('rakuda')
まずintroduce関数の引数にsay_name関数を渡して呼び出しintroに代入しています。introduce関数の戻り値はinner関数なのでintroは関数オブジェクトになります。inner関数は引数nameをとり、渡された関数say_nameをまず実行するため、「I’m rakuda.」が表示されます。その後、print文が実行されて「Nice to meet you.」が表示されます。
say_name関数には変更を加えずに、「Nice to meet you.」を表示する、という機能を加えることができました。さらに次のようにすると、関数に変更を加えずに機能を加える、というのがわかりやすいかもしれません。
say_name=introduce(say_name)
say_name('rakuda')
このようにintroduce関数の引数としてsay_nameを渡して、返り値を引数として渡した関数と同じ名前にしておきます。そして、この関数にinner関数の引数を渡して実行すると、あたかもsay_name関数が「I’m rakuda.」とそのあとの「Nice to meet you.」の部分を実行しているように見えますね。
デコレータ
デコレータで関数に機能を追加する
実は先の例でみたような、関数呼び出し時に引数として関数を渡して返り値を変数に代入する、ということをしなくても、関数に機能を追加することができます。それがデコレータです。同じことをデコレータでやってみましょう。
def introduce(func):
def inner(name):
func(name)
print('Nice to meet you.')
return inner
@introduce
def say_name(name):
print(f"I'm {name}.")
say_name('rakuda')
このようにsay_name関数を定義するときに、そのうえに「@introduce」と書くだけで、同じことができます。これがデコレータです。便利ですね!
他の例もみてみましょう。今度は名前と年齢をいう関数say_name_and_age関数を定義します。この関数に先ほどと同じようにデコレータ@intorduceをつけて呼び出してみましょう。
def introduce(func):
def inner(name):
func(name)
print('Nice to meet you.')
return inner
@introduce
def say_name_and_age(name,age):
print(f"I'm {name} and I'm {age}years old.")
say_name_and_age('rakuda',17)
今回はエラーが起きてしまいました。これはintroduce関数の返り値であるinner関数は引数nameを1つ取るように定義されています。それに対して、今回introduce関数に渡したsay_name_and_age関数は引数が2つあります。そのため、1つでよいのに2つも引数が与えられてますよ、とエラーがでています。この問題を解決するのが次の*argsと**kwargsです。
▶ *argsと**kwargsについて全く知らない、という方はまず以下の投稿をご覧ください。
*argsと**kwargsを使って汎用的にする
では先ほどのコードを修正しましょう。まず、introduce関数内で定義されているinner関数が、引数がいくつであっても受け取れるようにしましょう。そのうえで、introduce関数実行時に渡された関数が呼ばれたときに、その引数を受け取れるように書き換えます。
def introduce(func):
def inner(*args,**kwargs):
func(*args,**kwargs)
print('Nice to meet you.')
return inner
@introduce
def say_name_and_age(name,age):
print(f"I'm {name} and I'm {age}years old.")
say_name_and_age('rakuda',17)
今度はちゃんと動きましたね。say_name_and_age関数には@introduceというデコレータがついているので、まずintroduce関数が実行され、その際にsay_name_and_age関数が渡されます。introduce関数の返り値がinner関数なので、このinner関数に「’rakuda’」「17」という引数が渡されます。inner関数内部では、introduce関数呼び出し時に引数となったsay_name_and_age関数が実行されます。この際に、引数に「’rakuda’」「17」が渡されます。その後、print(‘Nice to meet you.’)が実行されています。複数の引数を内部的にタプルで保管するのが「*args」、辞書で保管するのが「**kwargs」でしたね。
まとめ
今回はデコレータを扱いました。デコレータは関数の内部を変更せずに、関数に機能を加えることができる機能です。便利ですね!同じデコレータを複数の関数につけることもあります。関数によって引数の数が異なるため、あらかじめデコレータは不特定数の引数を受け取れるように、*args、**kwargsで作る、ということをおさえておきましょう。
コメント