はじめに
今回はPython初学者向けにクロージャについて基本から解説していきます。クロージャは状態をキープした関数と表現されることがあります。「クロージャとはどういうものなのか」から始めて実際にコードを書きながら解説をしていきます。本記事を読むことでクロージャの概要を理解することができます。
関数の中で関数を定義する
クロージャを扱うまでに、まずは導入として関数の中で関数を定義した関数について解説をしておきましょう。このような関数は、「nested function」と呼ばれることもあります。また、関数の中で定義した関数を「inner function」と呼ぶこともあります。
実際にコードで例を見ていった方がわかりやすいですね。次のような関数を定義してみましょう。
# 関数の中で関数を定義
def greeting_func():
def say_hello():
print('こんにちは。今日も一日頑張りましょう')
return say_hello
関数「greeting_func」は関数の内部で「say_hello」関数を定義しています。「greeting_func」事態の返り値は「say_hello」関数です。なのでgreeting_funcを呼び出すと、say_hello関数のオブジェクトが返ってきます。この関数オブジェクトをコールすることもできます。
これを確認してみましょう。まずはgreeting_funcを呼び出して変数にいれます。この型を確認してみましょう。
# greeting_funcを呼び出して変数に格納し、その型を確認する
f=greeting_func()
print(type(f))
「greeting_func」のReturnは「say_hello」という関数です。なので、「greeting_func」を呼び出して代入した「f」は関数オブジェクトとなります。関数オブジェクトなので()をつけることで実行することもできます。やってみましょう。
# 関数オブジェクトを実行する
f()
「greeting_func」の内部で定義された「say_hello」が実行されてますね。このように関数もオブジェクトであるためReturnすることもできるし引数として渡すこともできるということを覚えておきましょう。
クロージャ
クロージャは状態を持った関数です。nested functionのメジャーな用途の1つです。これはコードで実際の例を追っていく方がわかりやすいでしょう。前節で「関数内で関数を定義し、その定義した関数をReturnする」例を見てきました。ここが理解できていれば簡単です。
状態をキープした関数のオブジェクト
「状態をキープした関数」といってもピンときませんね。実際の例をみるとわかりやすいと思います。まず次の関数を定義してください。
def plus_num(num):
def inner_plus_num(base):
return base + num
return inner_plus_num
この関数は引数numを受け取って「inner_plus_num」という関数を返す関数ですね。「innuer_plus_num」は、引数baseを受け取ってこれにnumを足してその値を返す関数となります。
「plus_num」に引数10を指定して呼び出してplus_10に代入してみましょう。
plus_10=plus_num(10)
plus_10
「plus_10」は関数オブジェクトになっています。これは「plus_num」という関数の返り値が関数なので当たり前ですね。引数numに10を指定したので、このオブジェクトは「inner_plus_num」関数の引数を指定すると、その引数に10を足して返してくれる関数となっています。試してみましょう。
print(plus_10(10))
print(plus_10(20))
print(plus_10(30))
このオブジェクトに引数10,20,30を指定すると、20,30,40が返ってきてますね。つまり引数に10を加える関数となっています。このようにはじめに「plus_num」という関数を呼び出すときに指定した「10」という値がキープされていて、plus_10オブジェクトは10を加える関数となっています。
同様にplus_num呼び出し時に「2」を指定すれば、2を加える関数となります。
plus_2=plus_num(2)
print(plus_2(10))
print(plus_2(20))
print(plus_2(30))
これが状態をキープした関数となります。10を指定して呼び出したときには10を加える関数、2を指定して呼び出したときには2を加える関数、というように、呼び出し時に指定した状態をキープする関数となっていますね。
状態を動的にした関数のオブジェクト
次に状態を動的にした場合も見ておきましょう。まずは次の関数を定義してください。
def plus_numlist():
nums=[]
def inner_plus_numlist(num):
nums.append(num)
return sum(nums)
return inner_plus_numlist
この関数は引数をとりません。関数が呼び出されると空のリストnumsと「inner_plus_numlist」が定義されます。そして、関数「inner_plus_numlist」が返ってきます。「inner_plus_numlist」では引数numをとり、これをリストnumsに加えて、合計の計算を返します。試してみましょう。
sum_list=plus_numlist()
for i in range(5):
print(sum_list(i))
少しややこしいですが、わかりますか?まずは「plus_numlist」を呼び出して「sum_list」に代入しています。この「sum_list」は関数オブジェクトです。最初の状態ではnumsというリストは空っぽです。ここでsum_listはnumという引数を指定して呼び出すと、リストnumsにその値numを加えて、リストの要素全体を足し合わせたものを返します。
なので、for文のところでは、
- 1回目はリストnumsに0が加えられて、合計値0が返ってくる
- 2回目はリストnumsに1が加えられて、合計値1が返ってくる
- 3回目はリストnumsに2が加えられて、合計値3が返ってくる
- 4回目はリストnumsに3が加えられて、合計値6が返ってくる
- 5回目はリストnumsに4が加えられて、合計値10が返ってくる
となっています。このように「sum_list」は実行されるたびに内部にあるリストnumsの状態が違います。これが状態が動的に変わる、ということです。
まとめ
今回はクロージャについて扱いました。関数もオブジェクトで、引数に渡すこともできるし返り値にもなります。関数はひとことでいうと、状態をキープした関数のことです。「状態」は静的な場合もあれば動的なものもあり、2つの例をみてきました。
だいぶややこしかったかと思いますが、実際にコードを書いて何度か自分で動かして試してみるとよいかと思います。
コメント