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

# 目次と前回の記事

https://qiita.com/ysgeso/items/2381dd4e3283cbed49a0

https://qiita.com/ysgeso/items/1ce7ecc51ae323b64bee

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

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

| リンク | 説明 |
|:--|:--|
| [marubatsu.py](https://github.com/ysgeso/marubatsu/blob/master/128/marubatsu.py)  | Marubatsu、Marubatsu_GUI クラスの定義|
| [ai.py](https://github.com/ysgeso/marubatsu/blob/master/128/ai.py)  |  AI に関する関数 |
| [test.py](https://github.com/ysgeso/marubatsu/blob/master/128/test.py)  |  テストに関する関数 |
|  [util.py](https://github.com/ysgeso/marubatsu/blob/master/128/util.py) |  ユーティリティ関数の定義。現在は `gui_play` のみ定義されている |
| [tree.py](https://github.com/ysgeso/marubatsu/blob/master/128/tree.py)  | ゲーム木に関する Node、Mbtree クラスの定義  |
| [gui.py](https://github.com/ysgeso/marubatsu/blob/master/128/gui.py)  |  GUI に関する処理を行う基底クラスとなる GUI クラスの定義  |

AI の一覧とこれまでに作成したデータファイルについては、下記の記事を参照して下さい。

https://qiita.com/ysgeso/items/10d1d01192c014173e4b

# 高階関数によるラッパー関数の作成

前回の記事では、__ラッパー関数__ を利用して __関数の機能を変更する方法__ を紹介しました。また、[前回の記事の最後](https://qiita.com/ysgeso/items/1ce7ecc51ae323b64bee#%E3%83%A9%E3%83%83%E3%83%97%E3%81%99%E3%82%8B%E9%96%A2%E6%95%B0%E3%82%92%E4%BB%AE%E5%BC%95%E6%95%B0%E3%81%AB%E4%BB%A3%E5%85%A5%E3%81%99%E3%82%8B%E3%83%A9%E3%83%83%E3%83%91%E3%83%BC%E9%96%A2%E6%95%B0)で下記のプログラムのように、ラッパー関数の __仮引数__ に __機能を変更する関数を代入__ することで、__任意の関数の機能を変更__ する方法を紹介しました。

```python
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` とは若干異なる実引数を記述する必要がある点で、使い勝手が異なります。

```python
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 では __全てのデータがオブジェクトで表現される__ ので、返されるのは関数の定義を表す[関数オブジェクト](https://qiita.com/ysgeso/items/cd3e1c0731b859bf5e2c#%E9%96%A2%E6%95%B0%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88)である

```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
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
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
```
</details>

下記のプログラムの 1 行目では、__`create_show_time`__ の __実引数に `sum` を記述__ して呼び出すことで、__`sum`__ に処理時間を計測する処理を加えた __ラッパー関数が返り値として返され__、それを __`sum_show_time` という変数に代入__ しています。[以前の記事](https://qiita.com/ysgeso/items/cd3e1c0731b859bf5e2c#%E5%A4%89%E6%95%B0%E3%81%AB%E9%96%A2%E6%95%B0%E3%82%92%E7%9B%B4%E6%8E%A5%E4%BB%A3%E5%85%A5%E3%81%99%E3%82%8B%E4%BE%8B)で説明したように、__変数に関数オブジェクトを代入__ すると、__変数が関数の機能を持つ__ ようになります。

その結果、__`sum_show_time`__ が __`sum` のラッパー関数__ になりますが、このラッパー関数は先ほどと異なり、__実引数に `sum` を記述する必要がない__ ので、2 行目のように __`sum` と同じ実引数を記述__ して呼び出すことができ、実行結果のように処理時間と合計が表示されます。

```python
sum_show_time = create_show_time(sum)
print(sum_show_time([10, 20]))
```

実行結果

```
0:00:00
30
```

[前回の記事](https://qiita.com/ysgeso/items/1ce7ecc51ae323b64bee#%E3%83%A9%E3%83%83%E3%83%97%E3%81%99%E3%82%8B%E9%96%A2%E6%95%B0%E3%82%92%E4%BB%AE%E5%BC%95%E6%95%B0%E3%81%AB%E4%BB%A3%E5%85%A5%E3%81%99%E3%82%8B%E3%83%A9%E3%83%83%E3%83%91%E3%83%BC%E9%96%A2%E6%95%B0)のノートでも言及しましたが、以下のいずれかの処理を行う関数のことを __高階関数__（high order function）と呼びます。上記の __`create_show_time`__ は、下記の両方の条件を満たすので __高階関数__ です。

* 関数を仮引数に代入する
* 関数を返り値として返す

### 関数を作成して返り値として返す高階関数の使い道

高階関数には様々な使い道があります。その中の一つに上記の `show_time` のように、__関数を必要に応じて好きなだけ定義__ することができるというものがあります。

__一般的__ には、__グローバル関数__ は __def 文__ によって以下のような方法で作成され、関数オブジェクトが代入された変数名を記述することで呼び出して利用することができます。

* `def 関数名(仮引数):` で始まる関数の定義を記述して実行すると、その関数の定義を表す関数オブジェクトが作成される
* 作成された関数オブジェクトは `def 関数名(仮引数):` で記述した「関数名」という名前のグローバル変数に代入される

上記の方法で記述された __グローバル関数の定義__ は一般的には __一度しか実行されない__ ので、__グローバル関数__ は基本的にプログラムに記述した __グローバル関数の定義の数だけ__ 作られません。

それに対し、関数の中でローカル関数を定義し、定義したローカル関数を返り値として返す高階関数は、その __高階関数を呼び出すたび__ に __いくつでも__ 関数オブジェクトを __作成して返り値として返す__ ことができるという利点があります。

例えば、__def 文__ によって __さまざまな関数に対する__ ラッパー関数を __定義する場合__ は、__ラップする関数の数だけ__ def 文 による __関数の定義を記述__ する必要があります。

一方、__`create_show_time`__ という高階関数は、__実引数にラップする関数を記述して呼び出す__ ことで、必要に応じて __いくつでもラッパー関数を作成__ することができます。

:::note
本記事ではまだ説明していませんが、Python では def 文以外にラムダ式という方法で関数オブジェクトを作成することができます。ラムダ式の詳細については下記のリンク先を参照して下さい。
:::

https://docs.python.org/ja/3/reference/expressions.html#lambda


### ラッパー関数の名前

高階関数が返り値として返した __関数オブジェクトを利用__ するためには、__返り値を変数に代入する必要__ がありますが、__複数のラッパー関数を作成__ して利用する際には、作成したラッパー関数を __区別する__ ために __異なる名前の変数に代入__ する必要があります。

例えば、`create_show_time` を利用して、組み込み関数 __`print`__ に対するラッパー関数を作成する場合は、下記のプログラムの 1 行目のように、__`sum` に対するラッパー関数__ を代入した __`sum_show_time`__ とは __異なる名前の変数__ に返り値を代入する必要があります。

```python
print_show_time = create_show_time(print)
print_show_time(1)
```

実行結果

```
1
0:00:00
```

# クロージャー

先程定義したラッパー関数を作成して返り値として返す下記の __`create_show_time`__ は、2 ~ 7 行目で定義された __`show_time`__ というローカル関数の __関数オブジェクトを返す__ 処理を行っていますが、このローカル関数 `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
```

それにも関わらず、下記のプログラムの 2 行目を実行すると、組み込み関数 `sum` に対して処理時間を表示する処理が行われる点に疑問を感じた人はいないでしょうか？

```python
sum_show_time = create_show_time(sum)
print(sum_show_time([10, 20]))
```

## ローカル名前空間と名前解決のおさらい

上記のプログラムが正しく動作するのは、__クロージャー__（closure、関数閉包）という仕組みがあるからです。クロージャーの仕組みを理解するためには、[以前の記事](https://qiita.com/ysgeso/items/cd3e1c0731b859bf5e2c#%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E5%90%8D%E5%89%8D%E7%A9%BA%E9%96%93)で説明した __名前空間__ の仕組みを正しく理解する必要がありますが、それらについてはかなり前の記事で説明したので簡単におさらいします。

[以前の記事](https://qiita.com/ysgeso/items/cd3e1c0731b859bf5e2c#%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E5%90%8D%E5%89%8D%E7%A9%BA%E9%96%93)で説明したように、__関数呼び出しが行われた際__ に、その関数のブロックの中で作られたローカル変数やローカル関数などの __名前を管理__ する __ローカル名前空間が新しく作成__ されます。また、そのローカル __名前空間を利用できる範囲__ を表す __スコープ__ はその関数の __仮引数とブロック__ になります。関数の中で定義された __ローカル関数が呼び出された場合も同様__ の処理が行われます。

`create_show_time` は少々ややこしいプログラムなので、話を分かりやすくするために下記のより簡単なプログラムを例に挙げて説明します。

下記のプログラムでは、__関数 `a`__ のブロックの中で __ローカル変数 `x`__ と __ローカル関数 `b`__ を定義しています。また、__ローカル関数 `b` は `print(x)` を実行する__ 処理を行います。

```python
1  def a():
2      x = 1
3      def b():
4          print(x)
5      b()
```

<details><summary><strong>行番号のないプログラム</strong></summary>

```python
def a():
    x = 1
    def b():
        print(x)
    b()
```
</details>

上記のプログラムを実行した後で、下記のプログラムのように __`a()` を実行__ して関数 `a` を呼び出すと、実行結果に `1` が表示されます。

```python
a()
```

実行結果

```
1
```

上記の `a()` を実行すると、下記のような手順で処理が行われます。


* __1 行目__：関数 `a` に対するローカル名前空間が作成される。そのスコープは下図のように 1 行目の `a` の `()` の中と `a` のブロックの 2 ~ 5 行目となる

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/a6e3aba4-6843-36c6-8490-24b3d85b855a.png" width="200">

* __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` が表示される。この仕組みについて忘れた方は、[以前の記事](https://qiita.com/ysgeso/items/cd3e1c0731b859bf5e2c#%E5%90%8D%E5%89%8D%E3%81%AE%E8%A1%9D%E7%AA%81%E3%81%A8%E5%90%8D%E5%89%8D%E8%A7%A3%E6%B1%BA)を復習すること
* 4 行目の処理が終了すると、`b` の関数呼び出しの処理がすべて完了するので、`b` の名前空間が削除される
* `b` の関数呼び出しを行った 5 行目に処理が戻る
* 5 行目の処理が終了すると、`a` の関数呼び出しの処理がすべて完了するので、`a` の名前空間が削除される

上記のプログラムでは、__4 行目の `print(x)`__ の処理を行う際に、__`a` の関数呼び出しの処理__ はまだ __終了していない__ ので、__`a` の名前空間は存在__ しています。そのため、4 行目で __`x` という名前__ を、__`a` の名前空間から探す点__ に違和感を感じる人はあまりいないのではないかと思います。

## `create_show_time` の場合の処理

次に、下記の 11 行目で、先程定義した __`create_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
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` を表す関数オブジェクトが __登録される__


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/245218c2-ab41-358e-37b8-7f56d4137aa1.png" width="300">

* __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`__ の関数呼び出しの __処理は完了__ しています。[以前の記事](https://qiita.com/ysgeso/items/cd3e1c0731b859bf5e2c#%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E5%90%8D%E5%89%8D%E7%A9%BA%E9%96%93)で __ローカル名前空間__ は、関数呼び出しの処理が __終了した時点で削除される__ と説明しましたが、それが事実であれば、__`func` という名前が登録__ されている __`create_show_time` の名前空間__ は、上記の 4 行目で `func(*args, **kwargs)` を実行した際には __削除されているはず__ です。しかし、実際には上記の 4 行目を実行すると、削除されたはずの __`create_show_time` の名前空間から__ `func` に代入された __`sum` を取り出して__、`sum(*args, **kwargs)` が実行されます。

つまり、__ローカル名前空間__ は、関数呼び出しの処理が __終了した時点で削除される__ という説明は、実は __正しくない場合がある__ ということです。

## クロージャーとその仕組み

上記のおさらいで説明したように、名前解決を行う際には __内側の名前空間から順番に名前を探す__ という処理を行います。そのため、__ローカル関数が呼び出された際__ に、その名前空間に __名前が登録されていない場合__ は、その __ローカル関数を定義した関数の名前空間を探す__ ことになります。そのような処理を行うことができるのは、関数の定義を実行して作成された __関数オブジェクトの中__ に、その __関数の定義を行った関数の名前空間の情報が記録されている__ からです。

:::note warn
このような、__関数の定義を実行した際__ の __名前空間の情報を記録__ する __関数オブジェクト__ のことを、__クロージャー__（closure、関数閉包）と呼びます。

__Python の関数はクロージャーの性質を持ちます__[^2]。
:::


:::note
このことは、[Python の公式ドキュメント](https://docs.python.org/ja/3/reference/compound_stmts.html#function)に下記のように説明されています。

関数定義を実行すると、現在のローカルな名前空間内で関数名を関数オブジェクト (関数の実行可能コードをくるむラッパー) に束縛します。__この関数オブジェクトには、関数が呼び出された際に使われるグローバルな名前空間として、現在のグローバルな名前空間への参照が入っています__。
:::

### 関数 `a` の場合のクロージャーの仕組み

言葉の説明ではわかりづらいと思いますので、具体例で説明します。例えば、先程の下記のプログラムを実行した場合の処理の流れを説明します。

```python
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` が呼び出されたに行われる処理の流れを説明します。

```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
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 は <strong>~ にする</strong> という意味を表す __接頭語__ で、closure の前に en をつけることで、中で定義した関数を __クロージャーにする__ いう意味を持ちます。

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

```python
1  def a():
2      x = 1
3      def b():
4          print(x)
5      b()
```

### クロージャー変数

クロージャーの関数の中で利用できる、__エンクロージャーの名前空間に登録されている変数__ や関数のことを __クロージャ変数__（closure variable）と呼びます。例えば、上記のプログラムの 4 行目の __`x` はクロージャ変数__ です。なお、エンクロージャーの __名前空間のスコープの外で定義された変数や関数__ は、クロージャー変数に __含みません__。例えば、上記の `b` のクロージャー変数には __グローバル変数__、__グローバル関数__、__組み込み変数__ は __含まれません__。そのため、4 行目の組み込み関数 `print` はクロージャー変数ではありません。

クロージャー変数の詳細については、下記のリンク先を参照して下さい。

https://docs.python.org/ja/3/glossary.html#term-closure-variable

### 自由変数

クロージャーの関数の中に記述された、クロージャーの __名前空間に登録されていない変数__（ローカルでない変数、関数）のことを、__自由変数__（free variable）と呼びます。自由変数は、クロージャー変数よりも広い概念で、クロージャー変数だけでなく、__グローバル変数__、__組み込み変数__ を __含みます__。

自由変数の詳細については、下記のリンク先を参照して下さい。

https://docs.python.org/ja/3/glossary.html#term-free-variable

## 名前空間の削除の仕組み

__名前空間__ が __どのような場合に削除されるか__ について理解するためには、__ガーベジコレクション__ の仕組みを理解する必要があります。

### ガーベジコレクションの仕組み

[以前の記事](https://qiita.com/ysgeso/items/c6d4a2da7b8118a5a143#list-%E3%81%AE%E8%A6%81%E7%B4%A0%E3%81%B8%E3%81%AE%E4%BB%A3%E5%85%A5%E5%87%A6%E7%90%86)のノートで簡単に言及しましたが、Python では __オブジェクトは__ 変数や list の要素などから __参照さなくなる__ と、プログラムから __利用する方法が無くなる__ ため、__ガーベジコレクション__ と呼ばれる処理で __自動的に削除__ されます。逆に言えば、__変数などに代入__ されて __参照されているオブジェクト__ は勝手に __削除されたりすることはありません__。

例えば、下記のプログラムの 1 行目で作成して __`a` に代入__ された __`[1, 2, 3]`__ という list を表すオブジェクトは、2 行目で __`a` に別のデータを代入__ すると、__どこからも参照されなくなってしまい__、二度とプログラムから利用できなくなるため、__ガーベージコレクションの対象となって削除されます__。

```python
a = [1, 2, 3]
a = 1
```

:::note
ガーベジコレクションはオブジェクトが参照されなくなった時点で即座に行われるわけではなく、Python が必要だと判断したタイミングで自動的に行われます。

時々 Python の処理が短い間で固まるように見えることがあるのは、ガーベジコレクションが行われている可能性が高いでしょう。
:::

一方、下記のプログラムは、2 行目で __`b` に__ 1 行目で作成された __`[1, 2, 3]` が代入される__ ので、3 行目で `a` に `1` を代入しても、__`b` から `[1, 2, 3]` が参照されている__ のでガーベジコレクションの __対象とはなりません__。

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

なお、オブジェクトが変数から直接参照されていなくても、変数が参照するオブジェクトを辿っていくことで __間接的に参照__ されていればガーベジコレクションの対象とはなりません。例えば下記のプログラムでは、__`b` は直接__ `[1, 2, 3]` を __参照していません__ が、`b` を利用して __`b[0]` から間接的__ に `[1, 2, 3]` を __参照できる__ ので、3 行目で `a` に `1` を代入しても `[1, 2, 3]` はガーベジコレクションの対象とはなりません。

```python
a = [1, 2, 3]
b = [a, 4, 5]
a = 1
```

### 名前空間の寿命

[以前の記事](https://qiita.com/ysgeso/items/cd3e1c0731b859bf5e2c#%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E5%90%8D%E5%89%8D%E7%A9%BA%E9%96%93)で、__関数呼び出しの処理が終了__ すると、__ローカルな名前空間は削除される__ と説明しました。実際に、下記の関数 `a` の呼び出しでは、__関数呼び出しが終了__ すると、`a` の __ローカルな名前空間__ はどこからも __参照されなくなる__ ため、ガーベジコレクションの対象となり、自動的に削除されます。

```python
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` の関数呼び出しの処理が終了しても、その名前空間はガーベジコレクションの対象とならず、__自動的に削除されません__。

```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
10
11  sum_show_time = create_show_time(sum)
12  print(sum_show_time([10, 20]))
```

:::note warn
名前空間は、その __名前空間が変数などから参照されている間__ は __削除されない__。
:::

上記のプログラムのような、返り値として返されたローカル関数の関数オブジェクトを変数に代入する以外の方法でも、ローカルな名前空間が削除されない場合があります。具体例についてはこの後で紹介します。

:::note
関数の中で __ローカル関数を定義しても__、下記のプログラムのように、__返り値として__ その関数オブジェクトを __返さない場合__ は、`a` の関数呼び出しの終了後に __`a` の名前空間を参照する方法がなくなります__。そのため、`a` の関数呼び出しの終了後にその名前空間はガーベジコレクティングによって __自動的に削除されます__。

```python
1  def a():
2      x = 1
3      def b():
4          print(x)
5      b()
6
7  a()
```
:::

:::note
下記のプログラムの 11 行目のように、`create_show_time` の __返り値を変数に代入しない場合__ は、`create_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
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` と同様__ です。

```python
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`__ の __名前空間を参照できる__ のでガーベジコレクションの対象となって自動的に __削除されることはありません__。

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

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

# 今回の記事のまとめ

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

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

| リンク | 説明 |
|:--|:--|
| [marubatsu.ipynb](https://github.com/ysgeso/marubatsu/blob/master/128/marubatsu.ipynb)  | 本記事で入力して実行した JupyterLab のファイル |

# 次回の記事

https://qiita.com/ysgeso/items/bd63433b7491d8a07ff5

[^1]:処理の流れを追う際に、`args`、`kwargs`、`starttime` に代入された値は重要ではないのでそれらの値についての記述は省略しました
[^2]:上記ではローカル関数の場合の説明を行いましたが、__グローバル関数の場合も同様__ で、グローバル関数の関数オブジェクトは、グローバル名前空間の情報を保持します