Pythonで〇×ゲームのAIを一から作成する　その14　名前空間とスコープの仕組み

# 目次と前回の記事

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

https://qiita.com/ysgeso/items/95282cb1532602799052

# 実装の進捗状況と前回までのおさらい

## 〇×ゲームの仕様と進捗状況

1. `正方形で区切られた 3 x 3 の 2 次元のゲーム盤上でゲームを行う`
2. `ゲーム開始時には、ゲーム盤の全てのマスは空になっている`
3. `2 人のプレイヤーが遊ぶゲームであり、一人は 〇 を、もう一人は × のマークを受け持つ`
4. __2 人のプレイヤーは、交互に空いている好きなマスに自分のマークを 1 つ置く__
5. 先手は 〇 のプレイヤーである
6. プレイヤーがマークを置いた結果、縦、横、斜めのいずれかの一直線の 3 マスに同じマークが並んだ場合、そのマークのプレイヤーの勝利とし、ゲームが終了する
7. すべてのマスが埋まった時にゲームの決着がついていない場合は引き分けとする

仕様の進捗状況は、以下のように表記します。

* 実装が完了した部分を `背景が灰色の長方形` で記述する
* 実装の一部が完了した部分を、__太字__ で記述する


## 前回までのおさらい

前回の記事では、「ゲーム盤の (x, y) のマスにマークを配置する」関数と、「ゲーム盤を初期化する」関数を定義し、それらの関数が正しく動作するかどうかを下記のプログラムを実行して確認しました。

```python
def place_mark(x, y, mark):
    if board[x][y] == " ":
        board[x][y] = mark
    else:
        print("(", x, ",", y, ") のマスにはマークが配置済です")

def initialize_board():
    board = [[" "] * 3 for x in range(3)]

initialize_board()    # ゲーム盤の初期化処理を行う関数を呼び出す
place_mark(0, 1, "〇")  # (0, 1) のマスに 〇 を配置する
print(board)
place_mark(0, 1, "×")   # (0, 1) のマスに × を配置する
print(board)
```
実行結果
```
( 0 , 1 ) のマスにはマークが配置済です
[[' ', '〇', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
( 0 , 1 ) のマスにはマークが配置済です
[[' ', '〇', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
```

実行結果から、ゲーム盤の初期化を行う `initialize_board` を呼び出したにも関わらず、その次の `place_mark(0, 1, "〇")` を実行すると「( 0 , 1 ) のマスにはマークが配置済です」という表示が行われました。そのため、上記のプログラムの __どこかにバグが存在する__ ことがわかります。

このバグが起きる理由を理解するためには、Python の __名前空間__ と __スコープ__ について学ぶ必要があります。今回の記事では、名前空間とスコープの __仕組み__ について説明し、次回の記事で名前空間とスコープに関する補足説明、上記のプログラムのバグの原因と修正方法について説明します。

今回の記事はかなり複雑な内容に思えるかもしれませんが、実際には __見た目ほどは複雑ではありません__。比較的 __単純なルール__ を覚えれば __複雑さを緩和__ することが出来るようになります。その点については次回の補足説明で説明します。今回の記事の内容がうまく理解できなくても、プログラミングの技術が向上すれば理解できるようになると思いますので、__理解できなかった人__ は __後で__ この記事を __読み直してみる__ と良いでしょう。

# 名前空間とスコープ

:::note
Python の名前空間とスコープについてより詳しく知りたい方は、下記の Python の公式のドキュメントを見て下さい。ただし、このドキュメントの内容を理解するためには、__ある程度の知識が必要__ になります。
:::

https://docs.python.org/ja/3/tutorial/classes.html#python-scopes-and-namespaces


## 名前空間の意味

上記のリンク先で説明されているように、名前空間（namespace）とは、プログラム内に記述した __名前からオブジェクトへ__ の __対応づけ__（mapping） のことを表します。

Python では、この名前に相当するものには、__変数名__、__関数名__ など[^1] がありますが、今回の記事では最初は __変数名に限定__ して __名前空間の説明__ を行います。

「その 7」の記事で説明したように、Python では、プログラムが扱う __データ__ を、__オブジェクト__ という形式で __管理__ しています。従って、プログラム内に記述した変数名からオブジェクトを対応づけるということは、__変数名から__、変数に __代入したデータ__ を __対応づける__ ことを意味します。

具体的には、__式__ に `a + 1` のような __変数名を記述__ した際に、その `a` に __代入されたデータ__ を使って __計算が行われる__ のは、__名前空間の仕組み__ を使って、`a` という __名前__ に __対応づけられたオブジェクト__ を __探し出し__、そのオブジェクトが管理するデータを取り出すという処理が行われているからです。

つまり、名前空間は「__変数名から__、Python の中でオブジェクトの形式で管理されている __特定のデータ__ を __対応づける__」という、__変数の機能を実現__ する __仕組み__ です。

:::note
対応づけには __方向性__ があり、__片方向__ の対応づけと、__双方向__ の対応づけの __2 種類__ に分類することが出来ます。Python の名前空間は __片方向__ の対応づけで、__名前__ から __オブジェクト__ という方向の対応づけが行われますが、オブジェクトから名前へという方向の対応づけは __行われません__。

また、対応づけは __1 対 1__、__1 対 多__、__多 対 1__、__多 対 多__ の 4 つに分類することが出来ます。Python の名前空間の場合は、__名前に対応づけられたオブジェクト__ は __1 つだけ__ ですが、__オブジェクト__ は __複数の名前から対応づけられる__ 場合があるので __多 対 1__ の対応づけです。
:::

## 名前空間の仕組み

「その 7」の記事で説明したように、__変数__ は __オブジェクトの id を格納__ することで、数値型、文字列型、list などの任意の __データを管理__ する __オブジェクト__ を __参照__ します。この __参照__ は、変数名からオブジェクトへの __対応づけ__ を __意味する__ ので、この __仕組み__ は __名前空間__ の仕組み __そのもの__ です。

:::note
__参照__ という用語は、参照元と参照先があることからわかるように、__方向性を含めた対応づけ__ と __同様の意味__ を持ちます。対応づけと同様の意味を持つ用語に、__関連付け__ という用語があります。
:::

分かりづらいと思いますので、図で説明します[^2]。以前の図では、個別の変数を 1 つの直方体で表現し、その直方体の中にオブジェクトの id を表示していましたが、今回の記事からは、下図のように、変数と、その変数が格納するオブジェクトの id を __表の形式で図示__ することにします。ただし、オブジェクトの id が __参照__ するオブジェクトを __赤い点線で結ぶ__ 点は以前の図と同様です。

:::note
実際には、名前空間を表すデータもオブジェクトで管理されています。その点については今後の記事で詳しく説明します。
:::

下図は、`a = 0` と `b = 1` という代入文を実行した場合の名前空間とオブジェクトの図です。

名前からオブジェクトへの対応づけは、下図では __名前空間の表__ を使って、「__名前→オブジェクトの id →オブジェクト__」の手順で行われます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/f261ac53-084b-601b-21e1-0b3305de2730.png" width="350">

上記をまとめると以下のようになります。

:::note warn
名前空間とは、プログラムに __記述された名前から__、その名前が表す __データを管理するオブジェクト__ を __対応づける__、__対応表のような仕組み__ の事である。
:::

## 関数オブジェクト

Python では、__関数名__ も __名前空間__ が扱う __名前の一つ__ です。そのように思えないかもしれませんが、変数名と関数名はいずれも __同じ仕組み__ で __名前から特定のオブジェクト__ が __対応づけられます__。そのことを理解するためには、__関数オブジェクト__ について理解する必要があります。

コンピュータは、数値や文字列など、__あらゆるデータ__ を __2 進数のデジタルデータ__ で __表現__ します。__関数の定義__ も、数値型の `1` などと同様に、プログラムの中に文字を使って記述することができるので、__データの一種__ として考えることができます。実際に、Python では、__関数の定義__ も __2 進数のデジタルデータで表現__ され、関数を定義すると __そのデジタルデータを管理する__ オブジェクトが __新しく作成__ されます。そのような、__関数の定義を表すデータ__ を管理するオブジェクトのことを、__関数オブジェクト__ と呼びます。

Python では、__関数の定義__ が記述されている文が __実行される__ と、以下のような処理が行われます。

1. __関数の定義を表すデータ__ を管理する関数オブジェクトが __新しく作成される__
2. 関数の定義で記述した __関数名__ と __同じ名前の変数__ が __作成__ され、その中に新しく作成された __関数オブジェクトの id が格納__ される 

上記の説明からわかるように、__関数の定義__ では、__変数に関数を表すデータを代入__ するという処理が行われます。つまり、__代入文__ と __関数の定義__ は、いずれも __代入処理__ が行われるということです。変数と関数の違いは、代入された __データ__ が __関数の定義を表すデータ__ であるかどうかの __違い__ でしかありません。

このことを、実際にいくつかのプログラムで確認することにします。

### 関数に対応づけられたオブジェクトの id を表示する例

下記のプログラムでは、前回の記事で紹介した `place_mark` という __関数を定義__ していますが、この処理は `place_mark` という名前の __変数__ に、__関数オブジェクトの id を格納__ する処理です。従って、下記のプログラムのように、`id(place_mark)` を記述することで、`place_mark` に対応づけられた関数オブジェクトの id を取得することができます。

```python
def place_mark(x, y, mark):
    if board[x][y] == " ":
        board[x][y] = mark
    else:
        print("(", x, ",", y, ") のマスにはマークが配置済です")

print(id(place_mark))
```
実行結果
```
2180912782432
```

### 変数に関数を直接代入する例

__関数__ は、__変数の一種__ なので、__別の変数__ に代入文を使って __関数を代入する__ ことができます。下記のプログラムは、1、2 行目で、「2 つの仮引数 `x`、`y` の合計を計算して返り値として返す処理」を行う `add` という名前の関数を定義しています。その後の 4 行目で、`sum` という異なる名前の __変数__ に、`add` を __代入__ することで、`sum` と `add` が __同じ関数オブジェクトを共有__ するようになります。そのため、`sum` は `add` と __同じ機能を持つ関数__ になり、`sum` を使って __関数呼び出しを記述する__ ことができます。実際に、5 行目のように `sum(1, 2)` を `print` で表示すると `3` が表示されます。

```python
def add(x, y):
    return x + y

sum = add
print(sum(1, 2))
```
実行結果
```
3
```

:::note warn
前回の記事で、関数呼び出しを記述する際に、__実引数が存在しない場合__ でも __必ず `()` を記述しなければならない__ と説明した理由はここにあります。`()` を記述しなかった場合は、関数の定義に記述されたブロックの __処理を実行する__ という関数呼び出し __ではなく__、関数に代入された __関数の定義を表すデータ__ を表すことになるからです。
:::

:::note
現実の世界で例えると、関数呼び出しは「マニュアルに書かれている __指示に従って行動する事__」、関数の定義を表すデータは「マニュアル __そのもの__」に相当します。
:::

### 関数と同じ名前の変数にデータを代入する例

__関数は__、関数の定義を表す __データの一種__ で、__関数名と同じ名前__ の __変数__ に __代入__ されます。従って、__関数と同じ名前の変数__ に __別のデータを代入__ して __上書き__ することができます。下記のプログラムは、4 行目で __関数と同じ名前__ の `add` という変数に数値型の __`1` を代入__ しています。その結果、`add` は __関数ではなくなり__、5 行目のように `add` を表示すると `1` が表示されます。

```python
def add(x, y):
    return x + y

add = 1
print(add)
```
実行結果
```
1
```

:::note
上記のプログラムは、関数が __変数の一種であることを示す__ ために記述したものです。実際には、関数に、関数以外のデータを代入して上書きするような処理は、特別な理由がない限り、ほとんど記述することはありません。
:::

### 関数オブジェクトに関するまとめ

:::note warn
関数オブジェクトに関する内容をまとめると以下のようになります。

* __関数の定義を実行__ すると、__関数の定義を表すデータ__ を管理するオブジェクトが __新しく作成される__
* そのようなオブジェクトの事を __関数オブジェクト__ と呼ぶ
* 関数の定義に記述された __関数名と同じ名前の変数__ が作成され、その中に __関数オブジェクトの id が格納__ される
* 従って、関数の定義が記述された文を実行すると、__代入文と同じ処理__ が行われる
* 変数と関数の違いは、代入されたデータが、__関数の定義を表すデータであるかどうか__ である
* 関数は変数と同じ性質を持つので、__他の変数に代入__ したり、__他の値を代入__ することができる
:::

このように、変数名と関数名は、__同じ仕組み__ でオブジェクトに __対応づけられる__ ので、以後は「__名前__」という __用語__ を、__変数名と関数名の両方を指す__ という意味で使用します。

## 名前空間のスコープ

ここまでの説明で、名前空間という用語の中の「__名前__」に関する説明を行いました。次は「__空間__」という用語の意味について説明します。

空間を goo 辞書で引くと以下のような説明が記述されています。

「__物理学で、物体が存在し、現象の起こる場所__」

https://dictionary.goo.ne.jp/word/%E7%A9%BA%E9%96%93/#jn-60296

この説明は物理学での「空間」という用語の意味ですが、「名前空間」という用語から、「__名前__」に関する空間であることを考慮すると、この説明を「__名前が存在し、名前に関する処理が行われる場所__」のように読み替えることができます。

__名前が存在する場所__ は、__Python のプログラム__ なので、名前空間の「__空間__」は、__Python のプログラム__ を表すことになります。

先程の定義から、「空間」という用語には「__場所__」という意味があります。場所を goo 辞書で引くと以下のような説明が記述されています。

「何かが存在したり行われたりする所。__ある広がりをもった__ 土地」

https://dictionary.goo.ne.jp/word/%E5%A0%B4%E6%89%80/#jn-176113

この説明の中の、「__ある広がりをもった__」という説明から、「場所」には __範囲がある__ ことがわかります。このことから、名前空間には __範囲がある__ ことが読み取れます。

上記のことから、名前空間が「プログラムの中で、__変数名からオブジェクトへの対応づけ__ を、__適用する範囲__」の事を表すことがわかります。この名前空間を __適用する範囲__ のことを、__スコープ__ [^3]と呼びます。

## 名前空間の種類

名前空間にスコープがあるということは、__異なるスコープ__ を持つ __複数の名前空間__ が __存在する__ ということを意味します。実際に、Python の名前空間には、以下のような種類があります。

* __Python のプログラム全体__ を範囲とし、__組み込み__ 関数などの名前を管理する「__組み込み名前空間__」
* __モジュールのプログラム全体__ を範囲とする「__グローバル[^4]名前空間__」
* __関数[^5]の仮引数と、関数のブロック__ を範囲とする「__ローカル[^6]名前空間__」

ビルドイン名前空間 __以外__ の名前空間は、Python のプログラムの実行中に、__新しく作成__ されたり、必要がなくなった時に __削除__ されたりします。

以下に、それぞれにの名前空間について説明します。

### 組み込み名前空間

__組み込み名前空間__ は、これまでのプログラムでも利用してきた、`print` や `id` などの __あらかじめ定義__ されている __組み込み（built in）定数や関数の名前__ から、その __データを管理するオブジェクト__ を __対応づけ__ ます。組み込み定数や関数は Python のプログラムの __どこからでも利用できる__ ので、組み込み名前空間の __スコープ__ は、Python の __プログラム全体__ です。

:::note
論理型の __`True`__、__`False`__ や、データが存在しないことを表す __`None`__ は __組み込み定数__ です。変数ではなく、__定数__ と呼ばれるのは、これらの組み込み定数に別の値を代入することが __できない__ からです（代入しようとするとエラーになります）。
:::

組み込み名前空間は、Python のプログラムを __実行した直後__ に __自動的に作成__ され、プログラムの処理が終了するまで __存在し続けます__。また、組み込み名前空間が管理する __名前の一覧__ は、プログラムの実行中に __変化することはありません__。

Python の組み込み定数と関数の一覧については、下記のリンク先を参照して下さい。

https://docs.python.org/ja/3/library/constants.html

https://docs.python.org/ja/3/library/functions.html

### グローバル名前空間

__グローバル名前空間__ は、__モジュールに対して作られる__ ものなので、最初にモジュールについて説明します。

__モジュール__ は、__ファイルに記述__ された __Python のプログラム__ のことを表します。

Python のプログラムが記述された __ファイルを実行__ した場合は、そのプログラムの事を __メインモジュール__ と呼びます。また、メインモジュールには、`__main__` という名前が付けられます。

これは、VSCode などで __JupyterLab のファイル__ にプログラムを __記述して実行__ する場合も __同様__ で、そのプログラムは __メインモジュール__ になります。

__他のファイル__ に記述されたモジュールを自分のプログラムに __組み込んで利用する__ ことを、モジュールを __インポート__ すると呼びます。モジュールのインポートの記述方法については必要になった時点で説明します。

グローバル名前空間は、プログラムがモジュールを利用する際に、__モジュールごとに新しく作られます__。具体的には、以下の場合に新しいグローバル名前空間が作成されます。

* Python の __プログラムを実行__ すると、__メインモジュール__ のグローバル名前空間が新しく作成される
* モジュールを __インポート__ すると、__そのモジュールの__ グローバル名前空間が新しく作成される

従って、Python のプログラムを実行すると、__メインモジュール__ のグローバル名前空間を __含む__、少なくとも __1 つ以上__ のグローバル名前空間が __作られます__。

グローバル名前空間は、基本的にはプログラムの __処理が終了するまで存在し続けます__。

グローバル名前空間の __スコープ__ は、__モジュールのファイル__ に記述された Python の __プログラム__ です。

:::note
JupyterLab に記述して実行したプログラムは、プログラムを実行する __仮想環境を選択した時点で開始__ され、その際に __組み込み名前空間__ と、__メインモジュールのグローバル名前空間__ が __作成__ されます。

__その後で__ JupyterLab に記述して __実行したプログラム__ は、新しく実行されるのではなく、それまでに記述したメインモジュールのプログラムの __続きとして実行__ されるので、その際にグローバル名前空間が新しく作成されることは __ありません__。

JupyterLab で実行を開始したプログラムは、JupyterLab の __ファイルを閉じた時点で終了__ し、その際に __全ての名前空間が削除__ されます。

他にも、JupyterLab のタブの上部にある「__再起動__」ボタンをクリックすることで、JupyterLab で実行されたプログラムを、ファイルを閉じることなく __強制的に終了__ することができます。「再起動」ボタンを押した後で、JupyterLab に記述された __プログラムを実行する__ と、__新しい Python のプログラムが実行__ され、__組み込み名前空間__ と、__メインモジュールのグローバル名前空間__ が __新しく作成__ されます。
:::

### ローカル名前空間

__ローカル名前空間__ は、主に __関数呼び出し__ が行われた際に __新しく作られ__、関数呼び出しの処理が __終了した時点で削除__ されます。

:::note
他にも、クラスの定義を実行した際にローカル名前空間が作成されます。その点については、今後の記事でクラスについて説明する際に、詳しく説明します。
:::

ローカル名前空間の __スコープ__ は、その関数の定義の中の __仮引数__ と、__関数のブロックの中に記述されたプログラム__ です。__関数名__ はローカル名前空間の __スコープに含まれない点__ に注意して下さい。

ローカル名前空間は、関数の定義の際に作られるの __ではなく__、__関数呼び出しが行われるたび__ に作られる点に注意して下さい。__同じ関数__ に対して __何度も関数呼び出しが行われた場合__ に、その関数に対するローカル名前空間を __使いまわす__ ことは __ありません__。同じ関数であっても、関数呼び出しが __行われるたび__ に、__何度でも__ ローカル名前空間が __新しく作られ__、関数呼び出しの処理が __終了した時点で廃棄されます__。

:::note
このことは、今後の記事で説明する予定の、__再起呼び出し__ という処理を正しく理解するために重要となります。
:::

:::note
Python では、__関数のブロック__ がローカル名前空間のスコープになりますが、for 文などのブロックはローカル名前空間のスコープにはなりません。一方、C 言語や、JavaScript などのプログラム言語では、関数のブロックだけではなく、__任意のブロック__ が __ローカル名前空間のスコープ__ になるものがあります。それらの言語を学んだことがある方は、混同しないように注意して下さい。
:::

### 名前空間の種類と性質のまとめ

:::note warn
名前空間の種類と性質を表にまとめると以下のようになります。

| 名前空間 | 作成時期 | 存続期間 | スコープ |
|:--|:--|:--|:--|
| __組み込み名前空間__  | プログラムを実行した時  | プログラムの実行が終了するまで  | Python のプログラム全体  |
| __メインモジュールのグローバル名前空間__  | プログラムを実行した時   |  プログラムの実行が終了するまで | 実行したプログラムが記述されたファイル  |
| __それ以外のグローバル名前空間__  | モジュールをインポートした時   |  プログラムの実行が終了するまで | インポートしたモジュールのファイル  |
| __ローカル名前空間__  |  関数呼び出しが行われた時 | 関数呼び出しの処理が終了するまで  | 関数の仮引数と関数のブロック  |
:::

## 名前空間のスコープの入れ子構造

名前空間のスコープは下記のような、__入れ子の構造__ になっています。なお、「ローカル名前空間のスコープ」のような表記は長いので、以後は「__ローカルスコープ__」のように、名前空間を省略して表記します。また、「__組み込み名前空間のスコープ__」は、一般的に「組み込みスコープ」ではなく、「__ビルトイン[^7]スコープ__」と表記されるようなので、本記事でもそれに倣うことにします。

__ビルトインスコープ ＞ グローバルスコープ ＞ ローカルスコープ__

下図は、名前空間の __スコープの入れ子構造__ を図示したものです。下図は、marubatsu.py というファイルに Python のプログラムを記述して実行した場合の図です。また、その際に marubatsu.py では、xxx.py と yyy.py という別のファイルに記述された Python のモジュールを利用しているものとします。なお、図の xxx.py と yyy.py の中に記述したプログラムに __特に意味はありません__。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/e1d1f12b-08db-c6f3-6364-15bcd1c817c3.png" width="700">

図からわかるように、それぞれの名前空間のスコープは以下のような構成になっています。

* __ビルトインスコープ__ は、__すべての__ Python のプログラムを __含む__
* __グローバルスコープ__ は、Python のプログラムを記述した __モジュールごとに作られる__
* __ローカルスコープ__ は、__関数の定義ごとに作られる__。ただし、__関数の名前__ はローカルスコープには __含まれない__

:::note
Python では、関数のブロックの中に __別の関数を定義__ することができます。そのような場合は、ローカル名前空間の __中に__、別の関数のローカル名前空間が __入れ子__ になった状態で作成されますが、話が複雑になるので、今回の記事ではそのような場合のことは説明しません。そのような場合の具体例については、必要になった時点で説明します。
:::

## 名前空間に関する表記

上図のように、__グローバル名前空間__ が __複数存在__ する場合で、それらを __区別する__ 必要がある __場合__ は、以後は「〇〇モジュールのグローバル名前空間」のように表記します。ただし、このような表記は長くなるので、__必ず存在__ する __メインモジュールのグローバル名前空間__ のことを、以降は __単に__ 「__グローバル名前空間__」と表記します。また、「__グローバルスコープ__」と __単に表記__ した場合も、__メインモジュールの__ グローバル名前空間の __スコープ__ の事を表すことにします。

ローカル名前空間を __区別する__ 必要がある __場合__ も同様に、関数の名前をつけて「`place_mark` のローカル名前空間」のように表記しますが、この表記も長いので、ローカル名前空間を __区別する必要がない__ 場合は、以後は __単に__ 「__ローカル名前空間__」や「__ローカルスコープ__」と表記します。

## 名前の衝突と名前解決

先程の図からわかるように、プログラムの文は、__最低でも__ ビルトインスコープと、グローバルスコープの __2 つ__ のスコープに __属しています__。それに加えて、関数の仮引数と、関数のブロックの中の文は、その関数の  __ローカルスコープにも__ 属します。

プログラムの文が __複数の名前空間__ のスコープに __属する__ ということは、その文に記述された __名前からオブジェクト__ を、__どの名前空間__ を使って対応づけるかを __決める必要がある__ ということです。

名前空間は、それぞれ __個別に__ 名前とオブジェクトの __対応づけ__ を行います。そのため、__異なる名前空間__ が、__同じ名前を管理__ する場合があります。例えば、以下のような状況が発生する可能性があります。なお、このような状況が発生する具体例については、この後で実際に紹介します。

* グローバル名前空間が、`a` という名前から `0` というデータを管理するオブジェクトを対応づける
* ローカル名前空間が、`a` という名前から `1` というデータを管理するオブジェクトを対応づける

上記のような状況では、プログラムに記述された `a` が、グローバル名前空間によってオブジェクトに対応づけられるか、ローカル名前空間によって対応づけられるかによって、その値が `0` であるか、`1` であるかが __異なります__。このように、名前が記述された文を __スコープとする__、__複数の名前空間__ が __同じ名前を管理__ するような状況の事を、__名前の衝突__ と呼びます。

名前の衝突が起きた場合は、その名前を、どの名前空間を使ってオブジェクトに対応づけるかを決める必要があります。そのような、__複数の名前空間__ の中から、__1 つ__ の名前空間を __選択して__ 名前からオブジェクトを __対応づける処理__ の事を、__名前解決__[^8] と呼びます。

__名前解決__ には、__厳密な手順__ が決まっており、その手順について説明します。

### 代入処理による名前空間への登録と更新

変数に値を代入するという処理は、__変数名から__、変数に代入するデータを管理する __オブジェクト__ を __対応づける__ 処理です。従って、__代入処理__ が行われると、名前空間に対して「変数名からオブジェクトへの対応づけ」の __登録、または更新__ が行われます。

その際に、対応づけの登録または更新は、__代入先の変数名__ が記述されている __文をスコープ__ とする __入れ子__ になった __名前空間__ のうち、__最も内側にある__ 名前空間に対して行われます。

組み込み名前空間 __以外__ の __名前空間__ は、作成された時点では __中身は空__ になっています。従って、変数に対して __初めて__ 代入処理が行われた場合は __登録__ が、そうでない場合は __更新__ が行われます。

Python では、`=` 演算子以外にも、下記の場合に __代入処理と同様の処理__ が行われます。従って、下記の処理を実行した __場合にも__、対応づけの登録または更新が行われます.

* __関数の定義を実行__ した場合に、__関数名の変数__ に対して __関数の定義を表すデータ__ が代入される
* __関数呼び出し__ を行った場合に、__仮引数__ に __実引数__ が代入される

:::note warn
上記をまとめると、以下のようになります。

Python では下記の文を実行した場合に __代入処理__ が行われる

* `=` 演算子による代入文
* 関数の定義
* 関数呼び出し

__代入処理__ が行われた場合は、以下のような手順で処理が行われる

1. __代入先の変数名__ が記述されている __文をスコープ__ とする __入れ子__ になった __名前空間__ のうち、__最も内側にある__ 名前空間を __選択__ する
2. 選択した名前空間の中から、__代入先の変数名を探す__
3. __見つからなかった場合__ は、選択した名前空間に「__代入先の変数名から__、代入する値を管理する __オブジェクト__ への __対応づけ__」を __登録__ し、__見つかった場合__ は __更新__ する
:::

この説明では意味が分からない人もいるかと思いますので、少し後で具体例と図を使って説明します。

### 式の中に記述された名前解決

__名前が記述された式__ を実行した場合は、その __名前解決__ は以下の手順で行われ、対応づけられたオブジェクトから __データが取り出されて式の計算__ が行われます。

1. 名前が記述されている __式をスコープ__ とする __入れ子__ になった __名前空間__ のうち、__最も内側にある__ 名前空間を __選択__ する
2. 選択した名前空間の中から __名前を探す__
3. 名前が __見つかった__ 場合は、その名前空間を使って、名前からオブジェクトを __対応づける__
4. 名前が __見つからなかった__ 場合は、__一つ外側__ の名前空間を __選択__ して、手順 2 へ戻る
5. 手順 4 で、外側の名前空間が __存在しない場合__ は、__NameError__ という __エラーが発生__ する

:::note warn
__式の中__ に記述された __名前解決__ の手順をまとめると、以下のようになります。

__内側の__ 名前空間から __順に__ 名前を探し、__最初に見つかった__ 名前空間を使って名前からオブジェクトを __対応づける__
:::

### 名前の呼び方と名前のスコープ

名前解決を行うことによって、プログラムに記述された __名前__ を、__どの名前空間を使ってオブジェクトに対応づける__ かが決まります。そこで、以降は変数や関数などの __名前が__、どの名前空間を使って対応づけられるかが __明確になるように__、「__組み込み関数__」、「__グローバル変数__」、「__ローカル変数__」のように表記します。

また、プログラムに記述した __名前__ は、その名前に対応する __名前空間のスコープ内でのみ__ 同じオブジェクトに対応づけられます。そこで、プログラムの中で、名前が __同じオブジェクト__ に対応づけられる  __範囲__ のことを、「__変数のスコープ__」のように表記します。

:::note warn
同じ名前であっても、__異なる名前空間__ によってオブジェクトに __対応づけられる__ 場合は、__異なるものです__。このことは、__非常に重要なこと__ なので、忘れないようにして下さい。
:::

## 名前解決の具体例

言葉だけではわかりにくいと思いますので、名前解決で行われる処理を、図を使いながら、具体例を挙げて説明します。

下記のプログラムは、前回の記事で、関数の処理を説明する際に使ったプログラムです。

```python
def assign_one(x):
    x = 1

a = 0
assign_one(a)
print(a)
```
実行結果
```
0
```

このプログラムの関数の仮引数の名前を `x` から `a` に変更しても、__行われる処理は全く変わりません__ が、プログラムで行われる処理が初心者にとっては急に __わかりづらくなります__。

下記のプログラムは、上記のプログラムの関数の仮引数の名前を __`x` から `a` に変更__ し、3 行目に __`print(a)` を追加した__ ものです。Python のプログラムの初心者がこのプログラムを見た場合、6 行目で `assign_one` を呼び出した後で、2 行目で `a` に `1` が代入されるので、3 行目の
 `print(a)` で `1` が表示されるのと同じように、7 行目の `print(a)` でも `1` が表示されると __勘違い__ する人が多いのではないかと思いますが、実際には __修正前__ のプログラムと __同じ__ ように、__`0` が表示__ されます。

このプログラムを実行した際に行われる処理を __1 行ずつ__、__名前空間の様子を図示__ しながら説明します。

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

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

```diff_python
def assign_one(a):
    a = 1
    print(a)

a = 0
assign_one(a)
print(a)
```
</details>

<details><summary><strong>修正箇所</strong></summary>

```diff_python
- def assign_one(x):
+ def assign_one(a):
-     x = 1
+     a = 1
+     print(a)

a = 0
assign_one(a)
print(a)
```
</details>

実行結果
```
1
0
```

### プログラムの開始直後の図

下図は、このプログラムを marubatsu.py というファイル[^9]に保存して実行を開始した時点での名前空間とオブジェクトの状態を図示したものです。なお、このプログラムでは、他のモジュールをインポート __しない__ ので、グローバル名前空間は __1 つしか存在しません__。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/661d37f1-4caa-745c-2f3e-c3e05996f6fc.png" width="700">

図では、左に __プログラムと名前空間のスコープ__ を、真ん中にそれぞれの __名前空間の内容__ を、右にプログラムの実行によって作成された __オブジェクト__ を表示します。

プログラムが実行された直後の状態では、`print` や `id` などの __組み込み関数__ の定義を管理する __オブジェクトが作成__ されており[^10]、組み込み名前空間の中には図のように `print` という __名前__ と __オブジェクトの id__ を記録することによって、__名前からオブジェクトへの対応づけ__ が行われています。

__図では省略__ していますが、組み込み名前空間には `id` などの、他の組み込み関数の名前からオブジェクトへの対応づけも存在しています。また、それらのデータを管理するオブジェクトも存在しています。

marubatsu.py のプログラムは __まだ 1 行も実行されていない__ ので、図の __グローバル名前空間__ には、__何も登録されていません__。

また、ローカル名前空間は、関数呼び出しが行われた際に作成されるので、図には __ローカル名前空間__ はまだ __存在していません__。

### 1 ~ 3 行目の関数の定義の処理

下図は、プログラムの 1 ~ 3 行目の __`assign_one` の定義を実行__ した直後の状態を図示したものです。以降は、プログラムの中の __新しく実行__ した部分と、名前空間の中で __更新__、または __変更__ された部分を __赤い文字と水色の背景色__ で表記します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/a89a24ab-e181-f400-92bd-8ecf2d790413.png" width="700">

先ほど説明したように、__関数の定義__ が記述された行が __実行__ されると、関数と __同じ名前の変数__ に、関数の定義を表す __データが代入__ されるという処理が行われることを思い出してください。

__代入処理__ が行われた場合の処理の手順を以下に再掲します。
1. __代入先の変数名__ が記述されている __文をスコープ__ とする __入れ子__ になった __名前空間__ のうち、__最も内側にある__ 名前空間を __選択__ する
2. 選択した名前空間の中から、__代入先の変数名を探す__
3. __見つからなかった場合__ は、選択した名前空間に「__代入先の変数名から__、代入する値を管理する __オブジェクト__ への __対応づけ__」を __登録__ し、__見つかった場合__ は __更新__ する

上記の手順を行う前に、`assign_one` の関数の定義を管理する、上図の ID が 852 のオブジェクトが __新しく作成__ されます。その後で、上記の手順に従って、`assign_one` の関数の定義の処理は、以下のような手順で行われます

1. 代入先の __変数名__ は、__`assign_one`__ で、__最も内側__ にある名前空間は __グローバル名前空間__ である
2. グローバル名前空間の中に、`assign_one` は __存在しない__
3. グローバル名前空間の中に、「`assign_one` という __名前から__、`assign_one` の関数の定義を管理する __オブジェクト__ への __対応づけ__」を __登録__ する

上記の手順を行った結果、上図のように、__グローバル名前空間__ に「`assign_one` という __名前から__ と、`assign_one` の関数の定義を管理する __オブジェクト__ への __対応づけ__」が __登録__ されます。従って、`assign_one` は __グローバル関数__ です。

:::note
上図では、1 ~ 3 行目が __ローカルスコープ__ を表すオレンジ色の枠で囲まれているので、ローカルスコープのように __見えるかもしれません__ が、ローカル名前空間が作成されるのは、__関数呼び出しが実行された時__ です。関数の定義は、関数呼び出しとは __異なる処理__ なので、この時点ではローカル名前空間は __存在しません__。
:::

### 5 行目の代入処理

下図は、プログラムの 5 行目の `a = 0` 実行した直後の状態を図示したものです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/2c9659b5-92c6-0aff-1e95-cfaf4c6a6879.png" width="700">

代入文が記述されているので、`a` という名前に対して、先ほどの `assign_one` の場合と __同様の手順__ で __代入処理__ が行われます。その結果、上図のように `0` を管理する ID が 199 のオブジェクトが新しく作成され、グローバル名前空間に「`a` という __名前から__、`a` に代入されたデータを管理する __オブジェクト__ への __対応づけ__」が登録されます。従って、この `a` は __グローバル変数__ です。

### 6 行目の式の処理

6 行目には、`assign_one(a)` という __式__ が記述されており、式の中には __`assign_one`__ と __`a`__ という __2 つの名前__ が記述されています。そのため、この 2 つの __名前解決__ を行う必要があります。

__名前が記述された式__ を実行した場合の __名前解決の手順__ を再掲します。

1. 名前が記述されている __式をスコープ__ とする __入れ子__ になった __名前空間__ のうち、__最も内側にある__ 名前空間を __選択__ する
2. 選択した名前空間の中から __名前を探す__
3. 名前が __見つかった__ 場合は、その名前空間を使って、名前からオブジェクトを __対応づける__
4. 名前が __見つからなかった__ 場合は、__一つ外側__ の名前空間を __選択__ して、手順 2 へ戻る
5. 手順 4 で、外側の名前空間が __存在しなかった場合__ は、__NameError__ という __エラーが発生__ する

上記の手順に従って、`assign_one` の名前解決は、以下のような手順で行われます

1. `assign_one` が記述された式をスコープとする、__最も内側__ にある名前空間は __グローバル名前空間__ である
2. __グローバル名前空間__ の中に、`assign_one` は __登録されている__
3. __グローバル名前空間__ から、`assign_one` に対応づけられたオブジェクトの id が 852 であることがわかる

`assign_one` の __名前解決__ を行った結果、`assign_one` に対応づけられたオブジェクトが管理する、__関数の定義のデータを利用__ することが出来るようになります。

`a` についても `assign_one` と __同様の手順__ で __名前解決__ が行われ、`a` に対応づけられたオブジェクトが管理する __`0` という数値型のデータを利用__ することが出来るようになります。

式の中に記述された __すべての名前解決が完了__ したので、名前解決によって得られたデータを使って、`assign_one(a)` という __関数呼び出しの処理を開始__ します。


:::note
前回の記事で、関数を __定義する前__ に __関数呼び出しを実行__ すると __エラーになる__ と説明しました。その理由は、関数の名前は、今回の記事で説明したように、__関数の定義が実行された時__ に、名前空間に __登録__ されるからです。下記のプログラムのように、関数の定義を実行するよりも __前に__ 関数呼び出しを実行すると、その時点ではその __関数の名前__ はグローバル名前空間にも、その外側にある組み込み名前空間にも __登録されておらず__、名前解決を __行うことができない__ ため NameError というエラーが発生します。

```python
a = 0
assign_one(a)

def assign_one(a):
    a = 1
```
実行結果
```
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[1], line 2
      1 a = 0
----> 2 assign_one(a)
      4 def assign_one(a):
      5     a = 1

NameError: name 'assign_one' is not defined
```

なお、上記のプログラムは、JupyterLab の上部にある __「再起動」ボタンをクリックしてから__ 実行して下さい。再起動せずに上記のプログラムを前のプログラムに続けて実行すると、__前のプログラム__ で `assign_one` が __定義されている__ ので __エラーは発生しません__。
:::

### 1 行目の仮引数に実引数を代入する処理

関数呼び出しが実行されると、最初に __関数の仮引数__ に、関数呼び出しの __実引数の値が代入__ されます。具体的には、`仮引数のa = 実引数のa` のような代入処理が行われます。

下図は、プログラムの 1 行目の仮引数 `a` に、6 行目の実引数 `a` を代入した直後の状態を図示したものです。__ローカル名前空間__ は、__関数呼び出し__ が行われた __直後に作成__ されます。下図の真ん中の下に __新しい__ ローカル名前空間が __表記__ されるようになったのは、そのことを表しています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/cae83460-f23a-a306-fac5-21cf6612eadb.png">

__代入処理__ が行われるので、__仮引数の `a`__ という __名前__ に対して、先ほどの `assign_one` や 5 行目の `a` の場合と __同様の手順__ で処理が行われます。ただし、先ほどの場合と異なり、仮引数の `a` が記述されている行の、__最も内側__ にある名前空間は、__ローカル名前空間__ です。

ローカル名前空間は、関数呼び出しによって __作成された直後__ なので、この時点では __中身は空__ になっています。従って、ローカル名前空間には `a` という名前は __登録されていません__。

上記の事から、「仮引数 `a` に 実引数 `a`を代入」する処理によって、上図のように __ローカル名前空間__ に「__`a` という名前から__、`a` に代入された __グローバル変数__ である __実引数 `a` が参照するオブジェクト__ への __対応づけ__」が __登録__ されます。従って、仮引数の `a` は __ローカル変数__ です。

上図からわかるように、__`a` という名前__ はグローバル名前空間と、ローカル名前空間の __両方に登録__ されていますが、それぞれの名前からオブジェクトへの __対応づけ__ は、__異なる名前空間__ に登録されているので、「__グローバル変数 `a`__」と「__ローカル変数 `a`__」は、__名前は同じ__ でも、__異なる変数__ です。この時点ではこの 2 つの変数は、同じオブジェクトを共有していますが、この後の 2 行目の処理によって、この 2 つの __同じ `a` という名前の変数__ に、__異なる値が代入__ されます。

### 2 行目の代入処理

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/fec679ee-864e-90ae-bc21-9cead78fa572.png" width="700">

__代入処理__ が行われるので、代入する値である `1` を管理する ID が 620 のオブジェクトが新しく作成されます。

2 行目を __スコープ__ とする __最も内側__ にある名前空間は、__ローカル名前空間__ なので、ローカル名前空間の中から `a` という名前を探し、先ほど登録したばかりの `a` が見つかります。そのため、「ローカル名前空間の __`a` という名前から__ オブジェクトへの __対応づけ__」が、図のように `a` に代入された値を管理するオブジェクトに __更新__ されます。

その結果、グローバル変数 `a` とローカル変数 `a` の __データの共有が解除__ されます。図からわかるように、変更されるのはローカル変数 `a` だけで、グローバル変数 `a` の値は変更されません。

このことからも、__グローバル変数 `a`__ と __ローカル変数 `a`__ は __同じ名前__ でも __異なる変数__ であることを確認できます。

### 関数呼び出しの処理の終了

これで `assign_one` の関数呼び出しの処理が終了しますが、関数呼び出しの処理の中で `return 文` が実行されなかったので、この関数の返り値はデータが存在しないことを表す `None` になります。

そのため、6 行目の `assign_one(a)` が `None` に置き換わりますが、6 行目は __代入文ではない__ ので、置き換わった後で何らかの __処理が行われることはありません__。

また、関数呼び出しの処理が __終了__ したので、__ローカル名前空間__ はこの時点で __削除__ されます。その結果、__ローカル変数 `a`__ を __利用__ することは __できなくなります__。

下図は、関数呼び出しの処理が終了した直後の状態を図示したものです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/cd567a99-16dd-290f-bf8e-ffc5bf3adf4d.png" width="700">

上図のように、図から __ローカル名前空間__ が __削除__ されています。なお、ローカル名前空間の表にあった `a` に対応づけられていたオブジェクトは、どの名前からも __対応づけられなくなった__ ので、__図から削除__ しました。

### 7 行目の式の処理

7 行目の式には、__`print`__ と __`a`__ という __2 つの名前__ が記述されているので、その __2 つの名前を解決__ する必要があります。

最初に、`a` の名前解決から説明します。7 行目をスコープとする __最も内側__ にある名前空間は、__グローバル名前空間__ なので、グローバル名前空間の中から __`a` という名前を探し__、ID が 199 のオブジェクトに __対応づけられている__ ことがわかります。その結果、そのオブジェクトが管理する __`0` という数値型のデータ__ を利用することが出来るようになります。

次に `print` の名前解決について説明します。`a` と同様に、__グローバル名前空間__ の中から、__`print` という名前を探し__ ますが、__見つけることが出来ません__。そこで、次にグローバル名前空間のすぐ外にある __組み込み名前空間__ から `print` を __探す__ ことになります。ビルドイン名前空間には、__`print` が登録__ されているので、`print` に対応づけられたオブジェクトを見つけることができます。従って、`print` は __組み込み関数__ です。

`print` に対応づけられたオブジェクトと、`a` 対応づけられたオブジェクトが管理するデータを使って、`print(a)` という関数呼び出しの処理を開始します。結果として、画面に __グローバル変数 `a`__ の値である __`0` が表示__ されます。

## 次回の記事について

以上が上記のプログラムで行われる処理の説明ですが、この説明を読んだ方は、プログラムに __名前を記述するたびに__、名前解決がどの名前空間によって行われるかを、上記で説明したような手順に従って __調べる必要があるのか__ と、うんざりした人がいるかもしれません。また、名前空間は複雑なだけで、__メリットが感じられない__ という人がいるかもしれません。

実際には、名前解決がどの名前空間によって行われるかは、プログラムを __工夫して記述する__ ことで、__比較的簡単に見分ける__ ことが出来るため、あまり __心配する必要はありません__。今回の記事で図を使って名前解決の処理の手順について詳しく説明したのは、ある程度 __複雑なプログラム__ を自分で記述したり、他人が書いたプログラムを __理解する__ 際に、名前解決の __仕組みを正しく理解__ しておくことが __重要になる場合__ があるためです。

次回の記事では、名前解決がどの名前空間によって行われるかを __見分ける手順__ と、名前解決を __見分けやすい__ プログラムの __記述方法__ について説明します。

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

以下のリンクから、本記事で入力して実行した JupyterLab のファイルを見ることができます。

https://github.com/ysgeso/marubatsu/blob/master/014/marubatsu.ipynb

# 次回の記事

https://qiita.com/ysgeso/items/881dd00ab183aedd8cc4

# 更新履歴

| 更新日時 | 更新内容 |
|:--|:--|
| 2023/9/29 | for y in range(3)　→　for x in range(3)<br>ブロック　→　関数のブロック<br>ビルトイン名前空間　→　組み込み名前空間<br>「関数と同じ名前の変数にデータを代入する例」の説明の修正 |

[^1]:他にも本記事ではまだ説明していない「クラス名」や「モジュール名」などがありますが、いずれも __同じ仕組み__ で特定の  __オブジェクトと対応__ づけられます
[^2]:図でのオブジェクトなどの表記の仕方については、「その 7」の記事を参照して下さい
[^3]:scope は「範囲」を表す英単語です
[^4]:global は「全体的な」を表す英単語です
[^5]:関数以外にも、今後の記事で説明するクラスを範囲とする場合があります
[^6]:local は「局所的な」を表す英単語です
[^7]:built in は「組み込み」を表す英単語です
[^8]:名前解決という用語は、ネットワークでドメイン名をIPアドレスに対応づけるという意味でも良く使われます。同じ用語でも本記事の用語とは意味が異なる点に注意して下さい
[^9]:JupyterLab で、marubatsu.ipynb というファイルに同じプログラムを記述して実行した場合でも、__本記事と同様の処理__ が行われます
[^10]:組み込み関数の定義の内容は __見ることができない__ ので、図では `print` の定義を管理するオブジェクトの中に「print 関数の定義」と表記しました