Python クロージャーの使い方

Python学習【365日チャレンジ!】55日目のマスターU(@Udemy11)です。

55という数字はやっぱりゴジラでしょ!

なんていいつつ、51のときは「イチローでしょ!」ということを忘れてしまっていました。

毎日学習している関係上、1から365までの数字を一つづつ見ることになるので、何かにつけて数字を紐解いていきたいなと思いつつも、数字がこれ以上大きくなったら野球は無理だろうな〜と冷静に考えている自分がいます。

数字で遊ぶのって意外と面白いですよね。

それでは、今日もPython学習頑張っていきましょう!

昨日の復習

昨日は、関数内関数を学習しました。
その名のとおり、関数の中に入った関数で、中の関数は外側の関数から引数をもらうことはできても、外の関数外で中の関数を実行することはできませんでした。
中の関数を何度も使いまわしたいというときに使われる関数ということでした。

def outer_func(x, y):
    def inner_func():
        print(x + y)
    return inner_func()
 
outer_func(1, 2)

出力結果

3

outer_func関数を実行することによって、返り値がinner_func関数になって、中のinner_func関数が実行され、先に渡されていた引数1と2の足し算が出力されるということですね。

エクセルでよく使うif関数の入れ子と同じような感じなので、流れはなんとなく理解いただけているかと思います。

今日は、そんな関数内関数と似ているクロージャーを学習したいと思います。

クロージャー

クロージャーは、プログラミング言語における関数オブジェクトの一種で、外側の変数を記憶した関数オブジェクトです。

関数内関数では、アウター関数に代入された引数の値をインナー関数に代入して返り値を返していましたが、クロージャーは、最終的に関数が実行した返り値を返すのではなく、実行する一歩手前の情報が入った関数オブジェクトとなります。

言葉にするとよくわからないので、実際にコードをみてみましょう。

def outer(a, b):
    def inner():
        return a * b
    return inner
 	
r = outer(7, 2)
print(r)
 
l = r()
print(l)

出力結果

<function outer.<locals>.inner at 0x104a11268>
14

コードを見ると、outer関数内にあるinner関数で、outer関数の引数aとbをかけた値が返り値になっています。

outer関数の返り値は、inner関数の実行【inner()】ではなくオブジェクト【inner】になっているところが関数内関数とは違うところです。

outer(7, 2)を実行した返り値はinnerであり、変数rには、inner関数を実行する前の情報が入っているので、変数rをprint出力すれば、関数オブジェクトの情報が表示されます。

その後、 関数オブジェクトになっている変数rを実行した値が変数lなので、7×2の解である14が出力されました。

関数内関数とほぼ同じようなものですが、クロージャーで返されるものは、結果ではなく、途中の経過なので、最終的な関数の実行前にいくつかのパターンを用意しておきたいときなどに使われるということでした。

これまた説明だけではよくわからないので、実際にやってみましょう。

アウター引数を変える

長方形の面積を求める計算を考えてみましょう。

アウター関数で、横(width)をいくつか用意して、実行時にインナー関数に縦(hight)を入れて面積を求めるコードになります。

def area_func(width):
    def inner_func(hight):
        return width * hight
    return inner_func
 
w1 = area_func(10)
w2 = area_func(20)
w3 = area_func(30)
 
print(w1(10))
print(w2(10))
print(w3(10))

出力結果

100
200
300

area関数では、横の値を10,20,30にして関数オブジェクトにしたw1,w2, w3を用意して、あとは縦の値を入れると面積が出る状態にしています。

次に、それぞれの関数オブジェクトw1, w2, w3に縦の引数10を代入して、それぞれを出力しました。

出力結果はぞれぞれ、10 × 10 = 100, 20 × 10 = 200, 30 × 10 = 300となりました。

クロージャーの流れはなんとなくわかったのですが、具体的にどんなに便利なのかが理解できていない状態です。

なので、これからたくさんコードを見ていったときに、「あ〜〜!これのことね!」というのがわかればいいかなくらいの理解度でいいのかなと思います。。

インナーの引数を変える

先程は、アウター関数の横(width)の値を複数入れて、インナー関数の縦(hight)の値を固定して3回出力したので、今度は、横の値を固定して、縦の値を変えて出力してみました。

def area(width):
    def h_func(hight):
        return width * hight
    return h_func
 
w = area(10)
 
print(w(30))
print(w(20))
print(w(10))

出力結果

300
200
100

横(width)を10に固定したので、関数オブジェクトは一つだけでよくなりました。

あとは、縦(hight)にそれぞれの値を入れて出力すれば、3パターンの面積を求めることができました。

この理解でいいの?

いつも思っちゃうのが、「この理解でいいの?」ということです。

これから学習をすすめていく中で、間違った理解をしていると困るんじゃないかなと思いつつも、なんとなくこれでいいんだろうな〜という感覚を大切にしながら講座をすすめています。

もちろん、疑問に感じたことは質問されていないかチェックしたり、Webで検索してみたり、最終的には質問をしたりして、きちんと理解しているつもりですが、ちょっと不安に思うところもあるんですよね。

現時点では、こんな感じの開き直り理解!?でいいとおもっているんですが、これには理由があります。

今はあくまでそれぞれの部品を一つ一つこんなことをするパーツであると理解しているところで、それが組み合わさったときに、改めて「そうやってつながるのか!」と感じられるんじゃないかと思っています。

虫の目鳥の目で全体を俯瞰することが大切だという話をよく聞くかと思いますが、これに当てはめると、今は虫の目でプログラムを理解しているところで、そのうち鳥の目で全体を見渡すための基盤をしっかりと作って置くことが重要です。

虫の目で狭い範囲をず〜っとうろうろしていても疲れてしまうので、たまには、空に羽ばたいて全体像を掴んでみるのもいいかもしれません。

というところで、今日はここまで。

明日も元気に、Good Python!