Python学習【365日チャレンジ!】56日目のマスターU(@Udemy11)です。
最近、懸垂をして大胸筋を鍛えているのですが、ようやく10回続けてできるようになってきました。
はじめのうちは、3回位が限界だったのですが、やっぱり続けるのってすごいですね。
もともと若い頃にスポーツをしていたので、最初から10回を3セットとかやろうと思っていたのですが、あまりの筋力の落ち具合にびっくりしてしまいました。
懸垂もしっかりと継続して、大胸筋を鍛えたいと思います。
それでは、今日もPython学習をすすめましょう。
昨日の復習
昨日は、クロージャーを学習しました。
関数の中に関数を定義して、実行する一歩手前の関数オブジェクトがクロージャーということですが、外側の関数で定義されたものをいくつか準備した上で、実行するときに準備したものを活用するというような使い方ができました。
def circle_func(pi):
def rad_func(radius):
return pi * radius ** 2
return rad_func
c1 = circle_func(3.14)
c2 = circle_func(3.141592535)
c3 = c1(10)
c4 = c2(10)
print('半径10、πが3.14の円の面積は、', c3)
print('半径10、πが3.141592535の円の面積は、', c4)
出力結果
半径10、πが3.14の円の面積は、 314.0
半径10、πが3.141592535の円の面積は、 314.1592535
アウター関数の引数に円周率のパイを2パターン代入して、あとは円の半径radius
をインナー関数の引数に代入すれば、円の面積が計算されるクロージャーrad_func
を使って、半径10を代入する2つのパターンを出力しています。
クロージャーは、
外側の変数を記憶した関数オブジェクト
ですので、上記のコードの場合、外側の変数pi
を記憶して、実行時に呼び出して活用しています。
関数内関数との違いは、アウター関数でインナー関数を実行した返り値ではなく、実行できる関数オブジェクトを返すところでしたね。
それでは、デコレーション!?じゃなかった、デコレーターを学習しましょう!
デコレーター
デコレーターというくらいなので、デコレーションするんだろうな〜となんとなく考えています。
ちなみに、デコレーションの意味を調べてみると、【装飾、飾り】という答えが見つかりました。
Pythonのデコレーターもこの飾り付けに使うものという認識で大丈夫かと思いますが、関数の実行時の前後で他の処理を付け加えたいときに使うものということです。
よくわからないですよね。
私も最初は一体ぜんたいどういうことなのか、全く理解できなかったので、レクチャーを20回は繰り返して見てしまいました。
今でもきちんと理解できているのか不安ですが、頭の中で理解しようとするより、実際にコードを書いたほうが理解できるので、私なりに理解した内容を共有していきます。
関数の実行前後に何かをさせるということなので、イメージとしては次のような感じです。
def add_num(a, b):
return a + b
print('start')
r = add_num(1, 2)
print('end')
print(r)
出力結果
start
end
3
変数aとbを加算する関数add_num
を用意して、関数実行の前後にstart
とend
を出力しています。
実行結果の出力前後ではなく、実行前後なので、実行結果の3は最後にprint
>出力されています。
この関数add_num
をインナー関数にして、デコレーターを作ります。
もうこの時点でわけが分からなくなっちゃいますよね。もちろん私もわけがわかっていませんでしたのでご安心を。
デコレートする
先程の関数add_num
をデコレートしてみます。
def print_info(func):
def wrapper(*args):
print('Start')
result = func(*args)
print('End')
return result
return wrapper
def add_num(a, b):
return a + b
p = print_info(add_num)
r = p(10, 20)
print(r)
出力結果
Start
End
30
ちょっとややこしいのですが、このコードの流れをみてみましょう。
アウター関数print_info
にfunc
という引数をとる関数print_info
の返り値をインナー関数wrapper
の関数オブジェクトにして、引数をいくつでもタプル化できる*args
にした関数wrapper
を定義します。
インナー関数wrapper
では、アウター関数の引数名func
関数を変数result
にいれて、result
を返り値にして、その前後にStart
とEnd
を出力します。
この関数print_info
がデコレーターになるわけですが、よくわからないですよね。
この関数のあとに、デコレートされる関数add_num
が定義されていますが、これは変数aとbを足し算する関数です。
そして、実際の実行は、12行目のprint_info(add_num)
で実行され、引数func
にadd_num
が代入されて、クロージャーwrapper
が返り値として返されます。
返り値wrapper
がp
に代入されたのち、次の行のp(10, 20)
が実行されて、デコレータの中のStart
が出力された後、func(*args)
つまりadd_num(10, 20)
が実行されて変数result
に30が代入された後、End
が出力されます。
ここまでの動作がデコレーターである関数print_info
の実行プロセスです。
その後、返り値であるresult
の値(30)がr
に代入されて、最終行で出力されるという流れです。
一度や二度この流れを確認しただけでは、理解できないと思います。
私自身、こうして流れを書き出しながら、改めて理解を深められたくらいですので、実際のコードを書きながら理解するしかありません。
@をつけて関数名を記述
実際にデコレーターを使うときは、デコレートする関数の前に@
をつけて記述します。
def print_info(func):
def wrapper(*args):
print('Start')
result = func(*args)
print('End')
return result
return wrapper
@print_info
def add_num(a, b):
return a + b
r = add_num(10, 20)
print(r)
出力結果
Start
End
30
デコレーターは、Pythonの機能に標準で備わっているものなので、それだけ凡庸性の高い機能であると言えますが、今の時点ではどのような使い方ができるかは全く想像がつきません。
デコレーターは何が便利?
出力結果は同じなので、こんな複雑なコードを書く必要があるのかと疑問に思っちゃいませんか?
私もそう思います。
それではなぜこんな面倒なコードを書くのか?
それは、使い回しができるからです。
関数の実行前後にprint
出力する程度なら問題はありませんが、前後に何度も活用したい複雑な処理をさせたい場合などに使い回せるとかなりパフォーマンスが上がります。
今回書いたコードの場合は、新しい引き算をする関数や掛け算をする関数を定義して同じ前後の処理をしたいときは、新しい関数の前の行に@print_info
とつけるだけでできるので、前後の処理をコードにする必要がなくなります。
なかなか理解するのは難しいと思いますが、今の段階では、なんとなくこんな感じということを理解する程度で大丈夫です。
デコレータのネスト
次に、デコレーターの中にデコレーターを入れる処理をしてみます。
先程のprint_info
の中に別のprint
出力を加えたデコレータmore_info
を作ってみました。
def print_info(func):
def wrapper(*args):
print('Start')・・・・・①
result = func(*args)
print('End')・・・・・①'
return result
return wrapper
def more_info(func):
def wrapper(*args):
print('func:', func.__name__)・・・・・②
print('args:', args)・・・・・②
result = func(*args)
print('result:', result)・・・・・②'
return result
return wrapper
@more_info
@print_info
def add_num(a, b):
return a + b
r = add_num(10, 20)
print(r)
出力結果
func:wrapper
args:(10, 20)
Start
End
result:30
30
@more_info
の次の行に@print_info
を入れて、その下の行にデコレートされる関数を記述しています。
出力結果は次のような感じでデコレートされています。
外のデコレーター②func:wrapper
外のデコレーター②args:(10, 20)
中のデコレータ①Start
実行関数(実行だけで出力されていない)
中のデコレーター①’End
外のデコレーター②’result:30
実行結果30
外側のデコレーターが内部のデコレーターを包み込んで、中の関数をさらに包み込んでいるような感じです。
デコレーターは上の行にかかれているものが下の行のものを包み込むので、順番が入れ替わると結果も入れ替わるので、注意しておきましょう。
実際の使いみちを考えてみた
外側からサンドイッチするようなイメージだったので、実際にそんな構造になっているものを考えたところ、HTMLのコードがタグで挟まれているので、次のようなコードを考えてみました。
def deco_html(func):
def wrapper(*args):
result = '<html>' + str(func(*args)) + '</html>'
return result
return wrapper
def deco_body(func):
def wrapper(*args):
result = '<body>' + str(func(*args)) + '</body>'
return result
return wrapper
@deco_html
@deco_body
def main(text):
return text
s = main('hello')
print(s)
出力結果
<html><body>hello</body></html>
main(text)
をdeco_html
とdeco_body
でデコレートしています。
これまでやってきたように、実行関数の前後にprint
出力するだけだとHTMLタグの外側にテキストを出力してしまったので、HTMLタグとBODYタグをstr()
で文字列化したmain('hello')
の前後に加えてresult
に代入しました。
結果、うまい具合に2つのデコレーターに包まれた実行結果を得ることができました。
試行錯誤がポイント
最後のHTMLタグでデコレートする方法は、いろいろと失敗しながらようやく思った出力ができました。
func(*args)
がNoneType
だと怒られたり、最初に入れいた**kwargs
が不要だと気づいたり、なかなかうまく出力することができませんでした。
エラーが起こるとネットで調べて試してはエラーだったりを繰り返して、なんとか求めていた出力結果を得ることができました。
今回はかなりたくさんコードを書いては消してを繰り返したので、ある程度デコレーターを理解することができたような気がします。
ほんと考えたことが形になると嬉しくなっちゃいますよね。
といったところで、明日もしっかりとPython学習を続けたいと思います。
それでは明日も、Good Python!