目次と前回の記事
これまでに作成したモジュール
以下のリンクから、これまでに作成したモジュールを見ることができます。
リンク | 説明 |
---|---|
marubatsu.py | Marubatsu、Marubatsu_GUI クラスの定義 |
ai.py | AI に関する関数 |
test.py | テストに関する関数 |
util.py | ユーティリティ関数の定義。現在は gui_play のみ定義されている |
tree.py | ゲーム木に関する Node、Mbtree クラスの定義 |
gui.py | GUI に関する処理を行う基底クラスとなる GUI クラスの定義 |
AI の一覧とこれまでに作成したデータファイルについては、下記の記事を参照して下さい。
高階関数によるラッパー関数の作成
前回の記事では、ラッパー関数 を利用して 関数の機能を変更する方法 を紹介しました。また、前回の記事の最後で下記のプログラムのように、ラッパー関数の 仮引数 に 機能を変更する関数を代入 することで、任意の関数の機能を変更 する方法を紹介しました。
from datetime import datetime
def show_time(func, *args, **kwargs):
starttime = datetime.now()
retval = func(*args, **kwargs)
endtime = datetime.now()
print(endtime - starttime)
return retval
前回の記事で定義したラッパー関数の問題点
ただし、この方法で関数の機能を変更する場合は、ラッパー関数の仮引数に、ラップする関数の仮引数には存在しない func
が追加 されるため、ラップする関数を呼び出す方法と、ラッパー関数を 呼び出す方法が異ります。そのため、ラッパー関数の使い勝手 が、ラップする関数の使い勝手と 異なる という 問題が発生 します。
言葉の説明ではわかりづらいと思いますので、具体例を挙げて説明します。例えば、組み込み関数 sum
に対して 上記のラッパー関数 show_time
を使って 機能を変更する 場合は、下記のプログラムのように sum([10, 20])
という sum
の関数呼び出しに対するラッパー関数の呼び出しは、show_time(sum, [10, 20])
のように、sum
とは若干異なる実引数を記述する必要がある点で、使い勝手が異なります。
print("sum")
print(sum([10, 20]))
print("show_time")
print(show_time(sum, [10, 20]))
実行結果
sum
30
show_time
0:00:00
30
ラッパー関数を作成する関数
この問題を解決する方法として、ラッパー関数の呼び出し を show_time([10, 20])
のように記述することができれば、ラップする関数とラッパー関数を 同じように利用できるようになるので便利 です。そのようなラッパー関数を作成する方法として、以下のような関数を定義するという方法があります。
処理:指定した関数の機能を変更 する ラッパー関数を定義 して 返り値として返す
入力:func
という仮引数に、ラップ(機能を変更)する関数を代入する
出力:func
の機能を変更したラッパー関数を返す
下記は、処理時間を表示(show time)するという機能を追加したラッパー関数を作成(create)して返す処理を行う、create_show_time
という関数の定義です。
-
1 行目:ラップする関数を代入 する
func
という 仮引数を持つcreate_show_time
を定義する -
2 ~ 7 行目:先程の
show_time
と同じ処理を行うラッパー関数show_time
を、create_show_time
の ローカル関数として定義する。ただし、ラップする関数 はcreate_show_time
の仮引数func
に代入されている ので、先程のshow_time
の仮引数から、ラップする関数を代入する 仮引数func
を削除 する -
4 行目:ラップする関数
func
を呼び出す処理 のプログラムは 同じだが、このfunc
はラッパー関数show_time
の仮引数ではなく、ラッパー関数を作成するcreate_show_time
の仮引数 である点が異なる -
9 行目:
create_show_time
の 返り値 として、2 ~ 7 行目で定義したラッパー関数show_time
を返す。なお、Python では 全てのデータがオブジェクトで表現される ので、返されるのは関数の定義を表す関数オブジェクトである
1 def create_show_time(func):
2 def show_time(*args, **kwargs):
3 starttime = datetime.now()
4 retval = func(*args, **kwargs)
5 endtime = datetime.now()
6 print(endtime - starttime)
7 return retval
8
9 return show_time
行番号のないプログラム
def create_show_time(func):
def show_time(*args, **kwargs):
starttime = datetime.now()
retval = func(*args, **kwargs)
endtime = datetime.now()
print(endtime - starttime)
return retval
return show_time
下記のプログラムの 1 行目では、create_show_time
の 実引数に sum
を記述 して呼び出すことで、sum
に処理時間を計測する処理を加えた ラッパー関数が返り値として返され、それを sum_show_time
という変数に代入 しています。以前の記事で説明したように、変数に関数オブジェクトを代入 すると、変数が関数の機能を持つ ようになります。
その結果、sum_show_time
が sum
のラッパー関数 になりますが、このラッパー関数は先ほどと異なり、実引数に sum
を記述する必要がない ので、2 行目のように sum
と同じ実引数を記述 して呼び出すことができ、実行結果のように処理時間と合計が表示されます。
sum_show_time = create_show_time(sum)
print(sum_show_time([10, 20]))
実行結果
0:00:00
30
前回の記事のノートでも言及しましたが、以下のいずれかの処理を行う関数のことを 高階関数(high order function)と呼びます。上記の create_show_time
は、下記の両方の条件を満たすので 高階関数 です。
- 関数を仮引数に代入する
- 関数を返り値として返す
関数を作成して返り値として返す高階関数の使い道
高階関数には様々な使い道があります。その中の一つに上記の show_time
のように、関数を必要に応じて好きなだけ定義 することができるというものがあります。
一般的 には、グローバル関数 は def 文 によって以下のような方法で作成され、関数オブジェクトが代入された変数名を記述することで呼び出して利用することができます。
-
def 関数名(仮引数):
で始まる関数の定義を記述して実行すると、その関数の定義を表す関数オブジェクトが作成される - 作成された関数オブジェクトは
def 関数名(仮引数):
で記述した「関数名」という名前のグローバル変数に代入される
上記の方法で記述された グローバル関数の定義 は一般的には 一度しか実行されない ので、グローバル関数 は基本的にプログラムに記述した グローバル関数の定義の数だけ 作られません。
それに対し、関数の中でローカル関数を定義し、定義したローカル関数を返り値として返す高階関数は、その 高階関数を呼び出すたび に いくつでも 関数オブジェクトを 作成して返り値として返す ことができるという利点があります。
例えば、def 文 によって さまざまな関数に対する ラッパー関数を 定義する場合 は、ラップする関数の数だけ def 文 による 関数の定義を記述 する必要があります。
一方、create_show_time
という高階関数は、実引数にラップする関数を記述して呼び出す ことで、必要に応じて いくつでもラッパー関数を作成 することができます。
本記事ではまだ説明していませんが、Python では def 文以外にラムダ式という方法で関数オブジェクトを作成することができます。ラムダ式の詳細については下記のリンク先を参照して下さい。
ラッパー関数の名前
高階関数が返り値として返した 関数オブジェクトを利用 するためには、返り値を変数に代入する必要 がありますが、複数のラッパー関数を作成 して利用する際には、作成したラッパー関数を 区別する ために 異なる名前の変数に代入 する必要があります。
例えば、create_show_time
を利用して、組み込み関数 print
に対するラッパー関数を作成する場合は、下記のプログラムの 1 行目のように、sum
に対するラッパー関数 を代入した sum_show_time
とは 異なる名前の変数 に返り値を代入する必要があります。
print_show_time = create_show_time(print)
print_show_time(1)
実行結果
1
0:00:00
クロージャー
先程定義したラッパー関数を作成して返り値として返す下記の create_show_time
は、2 ~ 7 行目で定義された show_time
というローカル関数の 関数オブジェクトを返す 処理を行っていますが、このローカル関数 show_time
には ラップする関数を代入する仮引数は存在しません。
1 def create_show_time(func):
2 def show_time(*args, **kwargs):
3 starttime = datetime.now()
4 retval = func(*args, **kwargs)
5 endtime = datetime.now()
6 print(endtime - starttime)
7 return retval
8
9 return show_time
それにも関わらず、下記のプログラムの 2 行目を実行すると、組み込み関数 sum
に対して処理時間を表示する処理が行われる点に疑問を感じた人はいないでしょうか?
sum_show_time = create_show_time(sum)
print(sum_show_time([10, 20]))
ローカル名前空間と名前解決のおさらい
上記のプログラムが正しく動作するのは、クロージャー(closure、関数閉包)という仕組みがあるからです。クロージャーの仕組みを理解するためには、以前の記事で説明した 名前空間 の仕組みを正しく理解する必要がありますが、それらについてはかなり前の記事で説明したので簡単におさらいします。
以前の記事で説明したように、関数呼び出しが行われた際 に、その関数のブロックの中で作られたローカル変数やローカル関数などの 名前を管理 する ローカル名前空間が新しく作成 されます。また、そのローカル 名前空間を利用できる範囲 を表す スコープ はその関数の 仮引数とブロック になります。関数の中で定義された ローカル関数が呼び出された場合も同様 の処理が行われます。
create_show_time
は少々ややこしいプログラムなので、話を分かりやすくするために下記のより簡単なプログラムを例に挙げて説明します。
下記のプログラムでは、関数 a
のブロックの中で ローカル変数 x
と ローカル関数 b
を定義しています。また、ローカル関数 b
は print(x)
を実行する 処理を行います。
1 def a():
2 x = 1
3 def b():
4 print(x)
5 b()
行番号のないプログラム
def a():
x = 1
def b():
print(x)
b()
上記のプログラムを実行した後で、下記のプログラムのように a()
を実行 して関数 a
を呼び出すと、実行結果に 1
が表示されます。
a()
実行結果
1
上記の a()
を実行すると、下記のような手順で処理が行われます。
-
1 行目:関数
a
に対するローカル名前空間が作成される。そのスコープは下図のように 1 行目のa
の()
の中とa
のブロックの 2 ~ 5 行目となる
-
2 行目:ローカル変数
x
に1
が代入されるので、a
の名前空間にx
という名前とその値の1
を表すオブジェクトが登録される -
3、4 行目:ローカル関数
b
の定義が実行されるので、a
の名前空間にb
という名前とその値のb
の定義を表す関数オブジェクトが登録される -
5 行目:ローカル関数
b
を呼びだす -
3 行目:関数
b
に対するローカル名前空間が作成される。そのスコープは上図のように 3 行目のb
の()
の中とb
のブロックの 4 行目となる -
4 行目:
print
でx
を表示するが、x
はb
の名前空間に登録されていないので、b
のすぐ外側の名前空間を探す。4 行目 はa
の名前空間のスコープの範囲内で、a
にはx
という名前が登録されているので、a
の名前空間に登録されたx
の値である1
が表示される。この仕組みについて忘れた方は、以前の記事を復習すること - 4 行目の処理が終了すると、
b
の関数呼び出しの処理がすべて完了するので、b
の名前空間が削除される -
b
の関数呼び出しを行った 5 行目に処理が戻る - 5 行目の処理が終了すると、
a
の関数呼び出しの処理がすべて完了するので、a
の名前空間が削除される
上記のプログラムでは、4 行目の print(x)
の処理を行う際に、a
の関数呼び出しの処理 はまだ 終了していない ので、a
の名前空間は存在 しています。そのため、4 行目で x
という名前 を、a
の名前空間から探す点 に違和感を感じる人はあまりいないのではないかと思います。
create_show_time
の場合の処理
次に、下記の 11 行目で、先程定義した create_show_time
を呼び出した際に行われるの処理の手順を説明します。途中までは、上記で説明した処理と似たような処理が行われますが、途中から異なる処理が行われます。
1 def create_show_time(func):
2 def show_time(*args, **kwargs):
3 starttime = datetime.now()
4 retval = func(*args, **kwargs)
5 endtime = datetime.now()
6 print(endtime - starttime)
7 return retval
8
9 return show_time
10
11 sum_show_time = create_show_time(sum)
12 print(sum_show_time([10, 20]))
-
1 行目:関数
create_show_time
に対するローカル名前空間が作成される。そのスコープは下図のように 1 行目の()
の中とそのブロックの 2 ~ 9 行目となる。ローカル変数の一種である仮引数func
に、11 行目で実引数に記述したsum
が代入されるので、create_show_time
の名前空間 にfunc
という名前とその値のsum
を表す関数オブジェクトが 登録される
-
2 行目:ローカル関数
show_time
の定義が実行されるので、create_show_time
の名前空間にshow_time
という名前とshow_time
の関数オブジェクトが登録される -
9 行目:ローカル関数
show_time
の関数オブジェクトを返り値として返す。この時点でcreate_show_time
の呼び出しの処理が終了 する -
11 行目:返り値として返されたローカル関数
show_time
の関数オブジェクトをsum_show_time
に代入する -
12 行目:
sum_show_time([10, 20])
が実行され、2 行目のshow_time
が呼び出される -
2 行目:関数
show_time
に対するローカル名前空間が作成される。そのスコープは上図のように 2 行目の()
の中とそのブロックの 2 ~ 7 行目となる。show_time
の名前空間に仮引数args
とkwargs
の名前とその値が登録される1 -
3 行目:
show_time
の名前空間にstarttime
の名前とその値が登録される1 -
4 行目:
func(*args, **kwargs)
が実行されるので、args
、kwargs
、func
に対する名前解決する必要がある。args
、kwargs
はshow_time
の名前空間に登録されているので、それを使って名前解決を行う。func
については下記で説明する
上記の最後の 4 行目の処理 で func
の名前解決 を行う必要がありますが、先ほどの関数 a
の呼び出しの例での print(x)
の処理と異なり、4 行目の処理が行われる前 に 9 行目で create_show_time
の関数呼び出しの 処理は完了 しています。以前の記事で ローカル名前空間 は、関数呼び出しの処理が 終了した時点で削除される と説明しましたが、それが事実であれば、func
という名前が登録 されている create_show_time
の名前空間 は、上記の 4 行目で func(*args, **kwargs)
を実行した際には 削除されているはず です。しかし、実際には上記の 4 行目を実行すると、削除されたはずの create_show_time
の名前空間から func
に代入された sum
を取り出して、sum(*args, **kwargs)
が実行されます。
つまり、ローカル名前空間 は、関数呼び出しの処理が 終了した時点で削除される という説明は、実は 正しくない場合がある ということです。
クロージャーとその仕組み
上記のおさらいで説明したように、名前解決を行う際には 内側の名前空間から順番に名前を探す という処理を行います。そのため、ローカル関数が呼び出された際 に、その名前空間に 名前が登録されていない場合 は、その ローカル関数を定義した関数の名前空間を探す ことになります。そのような処理を行うことができるのは、関数の定義を実行して作成された 関数オブジェクトの中 に、その 関数の定義を行った関数の名前空間の情報が記録されている からです。
このような、関数の定義を実行した際 の 名前空間の情報を記録 する 関数オブジェクト のことを、クロージャー(closure、関数閉包)と呼びます。
Python の関数はクロージャーの性質を持ちます2。
このことは、Python の公式ドキュメントに下記のように説明されています。
関数定義を実行すると、現在のローカルな名前空間内で関数名を関数オブジェクト (関数の実行可能コードをくるむラッパー) に束縛します。この関数オブジェクトには、関数が呼び出された際に使われるグローバルな名前空間として、現在のグローバルな名前空間への参照が入っています。
関数 a
の場合のクロージャーの仕組み
言葉の説明ではわかりづらいと思いますので、具体例で説明します。例えば、先程の下記のプログラムを実行した場合の処理の流れを説明します。
1 def a():
2 x = 1
3 def b():
4 print(x)
5 b()
6
7 a()
-
7 行目:
a()
を実行して関数a
を呼び出す -
1 行目:
a
の名前空間が作成される -
2 行目:
a
の名前空間にx
とその値の1
が登録される -
3、4 行目:
a
の名前空間にb
とその値のb
の関数オブジェクトが登録される。その際にb
の関数オブジェクト には、a
の名前空間の情報が記録 されている -
5 行目:
b()
を実行して関数b
を呼び出す -
3 行目:
b
の名前空間さ作成される -
4 行目:
x
の名前解決を行う。b
の名前空間にはx
は登録されていないので、b
の関数オブジェクトに記録 されているa
の名前空間 からx
を探して1
を取り出す
関数 create_show_time
の場合のクロージャーの仕組み
次に、下記のプログラムの 11 行目を実行して create_show_time
が呼び出されたに行われる処理の流れを説明します。
1 def create_show_time(func):
2 def show_time(*args, **kwargs):
3 starttime = datetime.now()
4 retval = func(*args, **kwargs)
5 endtime = datetime.now()
6 print(endtime - starttime)
7 return retval
8
9 return show_time
10
11 sum_show_time = create_show_time(sum)
12 print(sum_show_time([10, 20]))
-
11 行目:
create_show_time(sum)
を実行して関数create_show_time
を呼び出す -
1 行目:
create_show_time
の名前空間が作成され、その名前空間にfunc
とその値であるsum
の関数オブジェクトを登録する -
2 ~ 7 行目:
create_show_time
の名前空間にshow_time
とその値の関数オブジェクトが登録される。その際にshow_time
の関数オブジェクト には、create_show_time
の名前空間の情報が記録 されている -
9 行目:
show_time
の関数オブジェクトを返り値として返す -
11 行目:
sum_show_time
にshow_time
の関数オブジェクトを代入する -
12 行目:
sum_show_time
に代入されたshow_time
呼び出す -
2 行目:
show_time
の名前空間が作成され、その名前空間にargs
、kwargs
とその値を登録する -
4 行目:
func
の名前解決を行う。show_time
の名前空間にはfunc
は登録されていないので、show_time
の関数オブジェクトに記録 されているcreate_show_time
の名前空間 からfunc
を探してsum
を取り出し、sum(*args, **kwargs)
を実行する
9 行目 で create_show_time
の処理は完了 していますが、create_show_time
の名前空間 は、show_time
の関数オブジェクトの中に記録されている ため、create_show_time
の処理が完了しても なくなることはありません。そのため、4 行目で create_show_time
の名前空間から func
に代入された sum
を取り出して利用することができます。
以上が、クロージャーの仕組みによって、create_show_time
によって作成された ラッパー関数が、ラップする関数を代入する 仮引数がなくても正しく動作する理由 です。
クロージャーに関する用語
クロージャー に関するいくつかの 用語を紹介 します。
エンクロージャー
クロージャー となるローカル関数を 定義する関数 のことを、エンクロージャー(enclosure)と呼びます。en は ~ にする という意味を表す 接頭語 で、closure の前に en をつけることで、中で定義した関数を クロージャーにする いう意味を持ちます。
例えば、下記のプログラムでは、関数 a
が b
というクロージャーを定義しているので エンクロージャー です。
1 def a():
2 x = 1
3 def b():
4 print(x)
5 b()
クロージャー変数
クロージャーの関数の中で利用できる、エンクロージャーの名前空間に登録されている変数 や関数のことを クロージャ変数(closure variable)と呼びます。例えば、上記のプログラムの 4 行目の x
はクロージャ変数 です。なお、エンクロージャーの 名前空間のスコープの外で定義された変数や関数 は、クロージャー変数に 含みません。例えば、上記の b
のクロージャー変数には グローバル変数、グローバル関数、組み込み変数 は 含まれません。そのため、4 行目の組み込み関数 print
はクロージャー変数ではありません。
クロージャー変数の詳細については、下記のリンク先を参照して下さい。
自由変数
クロージャーの関数の中に記述された、クロージャーの 名前空間に登録されていない変数(ローカルでない変数、関数)のことを、自由変数(free variable)と呼びます。自由変数は、クロージャー変数よりも広い概念で、クロージャー変数だけでなく、グローバル変数、組み込み変数 を 含みます。
自由変数の詳細については、下記のリンク先を参照して下さい。
名前空間の削除の仕組み
名前空間 が どのような場合に削除されるか について理解するためには、ガーベジコレクション の仕組みを理解する必要があります。
ガーベジコレクションの仕組み
以前の記事のノートで簡単に言及しましたが、Python では オブジェクトは 変数や list の要素などから 参照さなくなる と、プログラムから 利用する方法が無くなる ため、ガーベジコレクション と呼ばれる処理で 自動的に削除 されます。逆に言えば、変数などに代入 されて 参照されているオブジェクト は勝手に 削除されたりすることはありません。
例えば、下記のプログラムの 1 行目で作成して a
に代入 された [1, 2, 3]
という list を表すオブジェクトは、2 行目で a
に別のデータを代入 すると、どこからも参照されなくなってしまい、二度とプログラムから利用できなくなるため、ガーベージコレクションの対象となって削除されます。
a = [1, 2, 3]
a = 1
ガーベジコレクションはオブジェクトが参照されなくなった時点で即座に行われるわけではなく、Python が必要だと判断したタイミングで自動的に行われます。
時々 Python の処理が短い間で固まるように見えることがあるのは、ガーベジコレクションが行われている可能性が高いでしょう。
一方、下記のプログラムは、2 行目で b
に 1 行目で作成された [1, 2, 3]
が代入される ので、3 行目で a
に 1
を代入しても、b
から [1, 2, 3]
が参照されている のでガーベジコレクションの 対象とはなりません。
a = [1, 2, 3]
b = a
a = 1
なお、オブジェクトが変数から直接参照されていなくても、変数が参照するオブジェクトを辿っていくことで 間接的に参照 されていればガーベジコレクションの対象とはなりません。例えば下記のプログラムでは、b
は直接 [1, 2, 3]
を 参照していません が、b
を利用して b[0]
から間接的 に [1, 2, 3]
を 参照できる ので、3 行目で a
に 1
を代入しても [1, 2, 3]
はガーベジコレクションの対象とはなりません。
a = [1, 2, 3]
b = [a, 4, 5]
a = 1
名前空間の寿命
以前の記事で、関数呼び出しの処理が終了 すると、ローカルな名前空間は削除される と説明しました。実際に、下記の関数 a
の呼び出しでは、関数呼び出しが終了 すると、a
の ローカルな名前空間 はどこからも 参照されなくなる ため、ガーベジコレクションの対象となり、自動的に削除されます。
def a():
x = 1
一方、下記のプログラムの場合は、11 行目で create_show_time
を呼び出すと、2 ~ 7 行目で定義されたローカル関数 show_time
の関数オブジェクト が返り値として返ります。先ほど説明したように、show_time
の関数オブジェクト は create_show_time
の名前空間のデータを参照する ので、11 行目で sum_show_time
に代入された関数オブジェクトから create_show_time
の名前空間を参照 することができるため、create_show_time
の関数呼び出しの処理が終了しても、その名前空間はガーベジコレクションの対象とならず、自動的に削除されません。
1 def create_show_time(func):
2 def show_time(*args, **kwargs):
3 starttime = datetime.now()
4 retval = func(*args, **kwargs)
5 endtime = datetime.now()
6 print(endtime - starttime)
7 return retval
8
9 return show_time
10
11 sum_show_time = create_show_time(sum)
12 print(sum_show_time([10, 20]))
名前空間は、その 名前空間が変数などから参照されている間 は 削除されない。
上記のプログラムのような、返り値として返されたローカル関数の関数オブジェクトを変数に代入する以外の方法でも、ローカルな名前空間が削除されない場合があります。具体例についてはこの後で紹介します。
関数の中で ローカル関数を定義しても、下記のプログラムのように、返り値として その関数オブジェクトを 返さない場合 は、a
の関数呼び出しの終了後に a
の名前空間を参照する方法がなくなります。そのため、a
の関数呼び出しの終了後にその名前空間はガーベジコレクティングによって 自動的に削除されます。
1 def a():
2 x = 1
3 def b():
4 print(x)
5 b()
6
7 a()
下記のプログラムの 11 行目のように、create_show_time
の 返り値を変数に代入しない場合 は、create_show_time
の返り値を どこからも利用できなくなるため、その名前空間はガーベジコレクションによって 自動的に削除されます。
1 def create_show_time(func):
2 def show_time(*args, **kwargs):
3 starttime = datetime.now()
4 retval = func(*args, **kwargs)
5 endtime = datetime.now()
6 print(endtime - starttime)
7 return retval
8
9 return show_time
10
11 create_show_time(sum)
これまでの記事でのクロージャーの利用
実は、このようなクロージャーの仕組みはこれまでの記事で既に利用しています。それは、Mbtree_GUI
クラスなどの create_event_handler
の中で定義した イベントハンドラー です。ただし、その際には create_show_time
とは異なり、返り値で関数オブジェクトを返す以外の方法 でクロージャーの仕組みを利用しています。
Mbtree_GUI クラスの create_event_handler
では、下記のプログラムの 2 ~ 5 行目のように、ローカル関数 on_left_button_clicked
を定義 していますが、そのブロックの中で、クロージャー変数である self
を利用 しています。また、on_left_button_clicked
は create_event_handler
の呼び出しが 終了した後 で、マウスの左ボタンがクリックされた際に呼び出されるという意味では、create_show_time
の中で定義された ローカル関数 show_time
と同様 です。
1 def create_event_handler(self):
2 def on_left_button_clicked(b=None):
3 if self.selectednode.parent is not None:
4 self.selectednode = self.selectednode.parent
5 self.update_gui()
略
6 self.left_button.on_click(on_left_button_clicked)
略
ただし、create_event_handler
では、その中で定義した ローカル関数を返り値として返していない ので、先程の説明から create_event_handler
のローカル名前空間は削除されてしまうと思えるかもしれません。
create_event_handler
のローカル名前空間 が create_event_handler
の関数呼び出しの 終了後も残り続ける のは、上記の 6 行目で、← ボタンのウィジェットが代入されている self.left_button
に、on_left_button_clicked
のイベントハンドラを 結び付けている からです。この処理を行うことによって、self.left_button
の属性 の一つに on_left_button_clicked
が代入 されます。self.left_button
は create_event_handler
の関数呼び出しの処理が 終了した後も残り続ける ため、self.left_button
を通じて create_event_handler
の 名前空間を参照できる のでガーベジコレクションの対象となって自動的に 削除されることはありません。
以上の事から、クロージャーの機能を有効に利用する方法は以下のようになります。
クロージャーの機能を有効に利用 するためには、クロージャーの関数オブジェクトを 何らかの変数に代入して 参照できるようにする 必要がある。
今回の記事のまとめ
今回の記事では、ラッパー関数を作成して返り値として返す高階関数 を紹介し、その処理で行われていることを理解するために必要な クロージャーの仕組 みについて説明しました。次回の記事ではデコレーターを利用したラッパー関数を作成する方法を紹介し、デコレーターを使って AI の関数を拡張する予定です。
本記事で入力したプログラム
リンク | 説明 |
---|---|
marubatsu.ipynb | 本記事で入力して実行した JupyterLab のファイル |
次回の記事