こんにちはエンジニアとして、機械学習とサーバーサイドの実務をしているかるでねです。
いきなりですが皆さんPythonの「クロージャ」って知ってますか?
なかなか聞きなれない言葉で抵抗があるかもしれないですが、そこまで難しくなく、知っていると便利なので早速解説していきます!
「クロージャ」について調べる
まずは「クロージャ」について調べてみましょう。
意味を調べる以下のように出てきます。
閉鎖
何が「閉鎖」なのでしょうか?
次にWikipediaで調べてみます。
クロージャ(クロージャー、英語: closure)、関数閉包はプログラミング言語における関数オブジェクトの一種。いくつかの言語ではラムダ式や無名関数にて利用可能な機能・概念である。引数以外の変数を実行時の環境ではなく、自身が定義された環境(静的スコープ)において解決することを特徴とする。
https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AD%E3%83%BC%E3%82%B8%E3%83%A3
これだけではわかりづらいですね...。
簡単に一言で言えば以下のようになります。
関数の外にある変数の値を記憶して使いまわせる。
当たり前のように感じますが想像しているものとはちょっと違うと思うので、これから解説していきます。
「クロージャ」を使ってみる
「百聞は一見にしかず」という言葉があるように、まずは「クロージャ」についてみていきましょう。
def outer(number1: int):
def inner(number2: int):
return number1 + number2
return inner
関数の中に関数がありますね。
外側の関数の「outer」は引数で数字を受け取って、「inner」関数を返します。
内側の関数の「inner」では、「outer」同様引数で数字を受け取って、「outer」で受け取った数字と足し合わせた数字を返します。
では早速関数を実行してみましょう。
# ①
output = outer(10)
# ②
print(f"outputの中身を確認: {output}")
# outputの中身を確認: <function outer.<locals>.inner at 0x10d60f430>
# ③
print(f"innerを呼び出して20を渡す: {output(20)}")
# innerを呼び出して20を渡す: 30
① まずは「outer」関数に「10」を渡して、その結果を「output」変数に格納しています。
②「output」の中身を確認してみるとオブジェクトの情報が出力されてしまいましたね。
先程定義した関数を確認してみると、「outer」を実行すると「inner」関数を返しているのが確認できます。
ということは以下のように考えてもよさそうですね。
「inner」関数 = 「output」変数
③この仮説をもとに「output」変数に数字を渡して実行してみます。
結果としてはしっかり足し算して返してくれていますね!
上記の仮説は厳密には正しくないですが、このように考えてみてもよさそうですね。
驚くべき点は「outer」関数に渡した数字もしっかり覚えているということです。
前の章で「クロージャ」の意味が「閉鎖」でしたが、変数に値を保持しているという意味っぽいですね。
いかがでしょうか?「クロージャ」についてある程度理解できたのではないでしょうか?
より詳しく「クロージャ」について知りたい方はこの記事の一番下の章の「参考」からみてください。
では次にどんな場面で活用できるかみていきたいと思います。
活用例
「クロージャ」について知ることができたので、実際どのような場面で使われているのかを確認していきます。
内側の変数の値を変更
まずは先程定義した関数にさまざまな値を入れてみます。
def outer(number1: int):
def inner(number2: int):
return number1 + number2
return inner
output = outer(10)
print(f"innerを呼び出して20を渡す: {output(20)}")
print(f"innerを呼び出して20を渡す: {output(30)}")
print(f"innerを呼び出して20を渡す: {output(40)}")
その結果出力は以下のようになります。
innerを呼び出して20を渡す: 30
innerを呼び出して20を渡す: 40
innerを呼び出して20を渡す: 50
しっかり想定通りに実行されていますね。
呼び出す変数を複数用意
では次に外側の関数に複数の値を入れて使い回してみます。
def human(height: int):
def register(name: str, weight: int):
return f"{name}さんの身長は{str(height)}cm、体重は{str(weight)}kgです。"
return register
human_170 = human(170)
human_180 = human(180)
ここでは「身長」を外側の関数で受け取り、その「身長」に該当する人が内側の関数「名前、体重」を登録するようになっています。
170cmと180cmの人が登録できるようにしています。
では早速実行していきましょう。
print(human_170("たかし", 60))
print(human_180("まなぶ", 70))
print(human_170("りょうすけ", 65))
# たかしさんの身長は170cm、体重は60kgです。
# まなぶさんの身長は180cm、体重は70kgです。
# りょうすけさんの身長は170cm、体重は65kgです。
うまくできましたね。
わざわざ「クロージャ」を使う必要もなさそうですが、色々活用法が浮かんできそうではありませんか?
最初に渡した変数の値を変換
最後に最初に渡した値を変換してみましょう。
def main(number1):
# 数字を置き換え
def rep_num(number2):
nonlocal number1
number1 = number2
# 足し算
def add_num(number3):
return number1 + number3
return add_num, rep_num
add_func, rep_func = main(100)
print(f"add_func(add_num)関数を呼び出し: {add_func(50)}")
rep_func(10)
print(f"add_func(add_num)関数を呼び出し: {add_func(50)}")
最初に渡した値とは上のコードでの「number1」変数のことです。(1行目)
順番にコードを見ていきましょう。
def main(number1):
# 数字を置き換え
def rep_num(number2):
nonlocal number1
number1 = number2
# 足し算
def add_num(number3):
return number1 + number3
return add_num, rep_num
この部分は「rep_num」関数以外今まで変わりません。
そして「rep_num」関数が今回の章の肝になります。
「nonlocal」という見慣れないものがありますが、これは変更したい値を指定しています。
そしてその下で「number1」に新たな値を入れています。
add_func, rep_func = main(100)
print(f"add_func(add_num)関数を呼び出し: {add_func(50)}")
rep_func(10)
print(f"add_func(add_num)関数を呼び出し: {add_func(50)}")
ここも今までとほとんど変わりませんね。
今回は「main」関数を実行した後に返ってくる関数が2つあるので、受け取る変数も2つ用意しています。
途中に「rep_func」関数を置くことでしっかり値が変更されているか確認します。
では実行結果を見てみましょう。
add_func, rep_func = main(100)
print(f"add_func(add_num)関数を呼び出し: {add_func(50)}")
rep_func(10)
print(f"add_func(add_num)関数を呼び出し: {add_func(50)}")
# add_func(add_num)関数を呼び出し: 150
# add_func(add_num)関数を呼び出し: 60
ちゃんと「number1」の値が変更されていますね!
ぜひ自分でも試してみてください!
まとめ
今回は「クロージャ」について解説してきました。
「クロージャ」は一言で言えば以下のようになります。
関数の外にある変数の値を記憶して使いまわせる。
すぐに活用する場面は出てこないかもしれないですが、知っているのと知らないのとでは雲泥の差があるので、使う機会が訪れたら使えるように頭に入れておきましょう。
今回はここまでです!
参考
https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AD%E3%83%BC%E3%82%B8%E3%83%A3
https://qiita.com/st43/items/949747037030d8e171f4