のーずいだんぷ

主に自分用メモですが、もしかしたら誰かの役に立つかもしれません

Pythonの関数内関数とクロージャについて

今日も一日一文法シリーズ、やる   。

Pythonのリファレンスや、参考書を読んでこれは知らなかったなとか、うろ覚えだな…というものを個人的に紹介していく。 勉強中の方の役にたてば嬉しい。そんな趣旨ではじめた。 読者になってもらうと一緒に勉強できておすすめ(勧誘)

教材としてしばらく以下の本を使用する。 今回のトピックスでもここから見つけた。

入門 Python 3

入門 Python 3

関数内関数(inner function)とは?

以下のコードのように、関数内で定義された関数のことを指す。

>>> def out_func(val1):
...     def in_func(val2):
...             return val2 * 2
...     return in_func(val1)
... 
>>> out_func(3)
6

Pythonの関数のスコープはご存知の通り、外側とは別の値を参照しているのでこの場合in_func()を使うことはできない。

>>> in_func(2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'in_func' is not defined

関数内関数はどんなときに使えるのか?

これについては正直いいものが見つからなかった。ざっと考えた感じ以下のような場面で役立つのかもしれない。

  • 特定の関数でしか使用しない場合、実装をわかりやすくする。
  • 間違えて他で使用されたくない。

説明がしにくいが、以下の場合後者のほうがわかりやすい気がする。

>>> def wrapper_method(val):
...     if val==1:
...             # ちょっと長い処理
...     else:
...             # ちょっと長い処理
...     return result
...     if val==1:
...             result = get_picture_URL()
...     else:
...             result = get_movie_URL()
...     return result

正直内部で使用されているそれぞれの関数は別に外だししてもいい気はするが、今回のようにわかりやすい関数名がつけられているとすれば、 その後の処理を追わなくていい。ただこれだけだと、別に関数を外部で定義したやつ使えば同じこと…となるのでおそらく、他で使われたくない状況も重なったときに使うのだと思う。

ちなみに書籍では次のように紹介されていた。

関数内関数は、ループやコードの重複を避けるために役立つ。複数回実行される複雑な処理をほかの関数内で実行するのである。

これも単独だと、外だしでもいいじゃん?となるので他で使われたくない関数で使用するのだろうと思う。

クロージャとは?

今回の書籍には以下のように説明がある。

他の関数によって動的に生成される関数で、その関数の外で作られた変数の値を覚えておいたり、変えたりすることができる。

これは個人的にはざっくりした説明に感じるが、内容に誤りはないと思う。実はさっきの関数内関数はクロージャにもなる。 ちょっと例を見てみる。

>>> def outer(val1):
...     def inner():
...             return val1 * 3
...     return inner
... 
>>> a = outer(1)
>>> b = outer(2)
>>> a()
3
>>> b()
6
>>> a()
3
>>> 

おそらく初めて見る人には直感的でないだろうが、Pythonは全てがオブジェクトなので関数もまたオブジェクトである。 関数()は実は__call__()を呼び出すに過ぎず、今回のように()なしで使うと、まるで関数のインスタンス(そんな言葉はない)のような振る舞いとなる。 aとbに着目するとわかると思うが、それぞれの代入した値は別の値として保存されており、まさにインスタンス変数のような振る舞いをみせる(おそらくメモリレベルでは似たようなことになっていると思う)

ちなみにこれらは

>>> type(a)
<class 'function'>
>>> type(b)
<class 'function'>

functionクラス、つまり関数なのだが、このような状態をクロージャと呼ぶ。

これらのユースケースはなかなか思いつかないが、関数型プログラミングでは当然のように多用する重要な仕様らしい。 また関数型プログラミングを学んだときにはその時に学びなおしたい。

今日もまた、一歩Pythonistaに近づいてしまった…