0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonで〇×ゲームのAIを一から作成する その128 ラッパー関数を作成して返り値として返す高階関数とクロージャーの仕組み

Last updated at Posted at 2024-10-28

目次と前回の記事

これまでに作成したモジュール

以下のリンクから、これまでに作成したモジュールを見ることができます。

リンク 説明
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_timesum のラッパー関数 になりますが、このラッパー関数は先ほどと異なり、実引数に 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 を定義しています。また、ローカル関数 bprint(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 行目:ローカル変数 x1 が代入されるので、a の名前空間に x という名前とその値の 1 を表すオブジェクトが登録される
  • 3、4 行目:ローカル関数 b の定義が実行されるので、a の名前空間に b という名前とその値の b の定義を表す関数オブジェクトが登録される
  • 5 行目:ローカル関数 b を呼びだす
  • 3 行目:関数 b に対するローカル名前空間が作成される。そのスコープは上図のように 3 行目の b() の中と b のブロックの 4 行目となる
  • 4 行目printx を表示するが、xb の名前空間に登録されていないので、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 の名前空間に仮引数 argskwargs の名前とその値が登録される1
  • 3 行目show_time の名前空間に starttime の名前とその値が登録される1
  • 4 行目func(*args, **kwargs) が実行されるので、argskwargsfunc に対する名前解決する必要がある。argskwargsshow_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_timeshow_time の関数オブジェクトを代入する
  • 12 行目sum_show_time に代入された show_time 呼び出す
  • 2 行目show_time の名前空間が作成され、その名前空間に argskwargs とその値を登録する
  • 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 をつけることで、中で定義した関数を クロージャーにする いう意味を持ちます。

例えば、下記のプログラムでは、関数 ab というクロージャーを定義しているので エンクロージャー です。

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 行目で a1 を代入しても、b から [1, 2, 3] が参照されている のでガーベジコレクションの 対象とはなりません

a = [1, 2, 3]
b = a
a = 1

なお、オブジェクトが変数から直接参照されていなくても、変数が参照するオブジェクトを辿っていくことで 間接的に参照 されていればガーベジコレクションの対象とはなりません。例えば下記のプログラムでは、b は直接 [1, 2, 3]参照していません が、b を利用して b[0] から間接的[1, 2, 3]参照できる ので、3 行目で a1 を代入しても [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_clickedcreate_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_buttoncreate_event_handler の関数呼び出しの処理が 終了した後も残り続ける ため、self.left_button を通じて create_event_handler名前空間を参照できる のでガーベジコレクションの対象となって自動的に 削除されることはありません

以上の事から、クロージャーの機能を有効に利用する方法は以下のようになります。

クロージャーの機能を有効に利用 するためには、クロージャーの関数オブジェクトを 何らかの変数に代入して 参照できるようにする 必要がある。

今回の記事のまとめ

今回の記事では、ラッパー関数を作成して返り値として返す高階関数 を紹介し、その処理で行われていることを理解するために必要な クロージャーの仕組 みについて説明しました。次回の記事ではデコレーターを利用したラッパー関数を作成する方法を紹介し、デコレーターを使って AI の関数を拡張する予定です。

本記事で入力したプログラム

リンク 説明
marubatsu.ipynb 本記事で入力して実行した JupyterLab のファイル

次回の記事

  1. 処理の流れを追う際に、argskwargsstarttime に代入された値は重要ではないのでそれらの値についての記述は省略しました 2

  2. 上記ではローカル関数の場合の説明を行いましたが、グローバル関数の場合も同様 で、グローバル関数の関数オブジェクトは、グローバル名前空間の情報を保持します

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?