Pythonで〇×ゲームのAIを一から作成する　その15　名前解決を見分けやすいプログラムの記述方法

# 目次と前回の記事

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

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

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

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

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
今回の記事も長くなったため、上記のバグの原因と修正方法について説明することができませんでした。上記のプログラムのバグの原因と修正方法については次回の記事で説明します。
:::

# 名前空間とスコープに関する処理のまとめ

前回の記事で説明した、『名前空間への「名前からオブジェクトへの対応づけ」の登録、更新』の手順と、「式の中に記述された名前の名前解決」の手順は、一見するとかなり複雑そうに __見えるかもしれません__ が、__実際には__ それほど __複雑ではありません__。

それぞれで行われる処理をまとめると以下のようになります。

__名前空間への「名前からオブジェクトへの対応づけ」の登録、更新__

* 名前からオブジェクトへの対応づけは、以下の __代入処理__[^1] によって行われる
    * __`=` 演算子__ による代入処理
    * __関数の定義の実行__ の際に行われる、__関数名と同じ名前__ の __変数__ に、__関数の定義__ を表す __データを代入__ する処理
    * __関数呼び出し__ の際に行われる、__仮引数に実引数を代入__ する処理
* 対応づけは、代入処理をスコープとする名前空間のうち、__最も内側の名前空間__ に対して登録または更新が行われる
* 名前空間に対応づけが登録されていなければ __登録__ され、そうでなければ __更新__ される

:::note warn
要約すると、「名前空間への対応づけの登録、更新は、__代入処理__ が行われた際に、__最も内側の名前空間__ に対して行われる」のようになります。
:::

このことから、関数の __ブロックの中__ に記述した __代入文__ によって、__グローバル変数__ に値を __代入__ する __ことはできない__ ということがわかります[^2]。

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

* 式をスコープとする、__最も内側の名前空間__ を使って、名前からオブジェクトへの対応づけが行われる
* 名前空間に名前が登録されていない場合は、__内側__ の名前空間 __から順番__ に名前空間を探す
* どの名前空間にも __名前が登録されていない__ 場合は、__NameError__ という __エラーが発生__ する

:::note warn
要約すると、「名前解決は、__最も内側の名前空間__ から順番に名前を探し、__最初に見つかった__ オブジェクトに対応づける」のようになります。
:::

# 名前の見分け方

上記で説明したまとめから、プログラムに記述された __名前__ が、__どの名前空間によって管理__ されているかを、下記の手順で見分けることが出来ます。

1. __代入処理__ の __代入先の変数の名前__ は、__最も内側__ の名前空間が管理する[^2]

2. __関数の定義__ によって記述された __関数名__ は、基本的に __グローバル関数__ である[^3]

3. 変数名がどの名前空間によって管理されるかを、以下の手順で見分けることが出来る

    * 関数の __ブロック内__ に記述された __変数__ の場合
        1. __仮引数と同じ名前__ の変数は、__ローカル変数__ である
        2. __関数のブロック__ の中で __代入文によって値が代入された__ 変数は、__ローカル変数__ である
        3. 上記以外の変数で、__関数の定義の外__ で __代入文によって値が代入された__ 変数は __グローバル変数__ である
        4. 上記以外の変数で、組み込み名前空間に登録されていれば __組み込み定数__ または __組み込み関数__ である
        5. 上記以外の場合は __NameError__ という __エラー__ が発生する
    * 関数の __定義の外__ に記述された __変数__ の場合
        1. __関数の定義の外__ で __代入文によって値が代入された__ 変数は __グローバル変数__ である
        2. 上記以外の変数で、組み込み名前空間に登録されていれば __組み込み定数__ または __組み込み関数__ である
        3. 上記以外の場合は __NameError__ という __エラー__ が発生する

なお、__関数の定義の外__ の場合の __手順 1 ~ 3__ は、__関数のブロック内__ の場合の __手順 3 ~ 5__ と __同じ手順__ なので、上記の手順は見た目ほどは複雑ではありません。

また、それぞれの名前空間のスコープは、頭の中で下図のようなイメージを行うことで比較的簡単に把握することが出来ます。

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/741af092-0793-b4e2-ebf6-c4441636e30d.png)

図からわかるように、__最も内側の名前空間__ は、以下の表で判別することが出来ます。

| プログラム | 最も内側の名前空間 |
|:--|:--|
| 関数の仮引数  | ローカル名前空間  |
| 関数のブロック  | ローカル名前空間  |
| 上記以外  | グローバル名前空間  |

上記の表と、「ローカル名前空間の外にグローバル名前空間」、「グローバル名前空間の外に組み込み名前空間」、「組み込み名前空間の外にはなにもない」ということを理解していれば、名前解決で行われる処理が __それほど複雑ではない__ ことが実感できるのではないでしょうか？

# 名前解決に類似する現実の処理

ここまでの説明を読んでも、名前解決で行われる処理が、得体のしれない複雑な処理を行っているように見えるかもしれないので、我々が __普段の生活__ で行っている、__名前解決に類似する処理__ を紹介します。

プログラムで __異なる名前空間__ に __同じ名前が登録__ される __ことがある__ ように、現実世界でも __異なる集団__ に、__同じ名前__ の人が __存在する__ 場合があります。

人間にとっての __集団__ には様々なものが考えられますが、一般的には __最も身近__ な集団は __家族__ でしょう。また、家族より広い集団としては、近所の知り合い、学校のクラス、会社の同僚など、様々なものがあります。今回は、__身近な順__ に、「__家族__」、「__近所の知り合い__」、「__日本人__」という 3 つの集団を使って説明します。

__ある集団__ の中の __会話__ で、誰かの __名前だけ__[^4] を出した場合、最初は __その集団の中__ から __名前の心当たりを思い浮かべ__、心当たりがなければ、集団の範囲を __少しずつ広げていく__ のが一般的ではないでしょうか？

例えば、__家族__ の会話 で「__一郎__」という __名前だけ__ を出した場合、以下のような手順で人物を思い浮かべる（名前から人物を対応づける）のが一般的[^5]ではないかと思います。

1．__家族__ に「一郎」という名前の人がいれば、その人を思い浮かべる
2. いない場合は、__近所の知り合い__ に「一郎」という名前の人がいれば、その人を思い浮かべる
3. いない場合は、__日本人__ の有名人である野球選手の「イチロー」[^6]を知っていれば思い浮かべる
4. 上記のいずれでもなければ、 __誰も思い浮かべることが出来ない__

また、__近所の知り合い__ の会話で「一郎」という名前だけを出した場合は、上記の手順 2 から心当たりを探すのが一般的ではないかと思います。

人間は、__頭の中__ に「家族の名簿」、「近所の知り合いの名簿」、「日本人の名簿」のようなものを __記憶しており__、それぞれの名簿の中に __同じ名前の人物がいても__、上記のような手順で、__名前を特定の人物に対応づける__ ことが出来ます。この、頭に記憶した __複数の名簿から__、名前と人物を __対応づける__ 上記の __手順__ は、Python の __名前解決__ で行われる __手順__ に __似ている__ と思いませんか？


下記の表は、この例えと名前空間の用語の対応を示したものです。

| 例え | 名前空間 |
|:--|:--|
| 家族の名簿  |  ローカル名前空間 |
| 近所の知り合いの名簿  | グローバル名前空間  |
| 日本人の名簿  | ビルドイン名前空間  |
| 家族 | 関数 |
| 近所の知り合い | モジュール |
| 日本人 | プログラム全体 |
| 人物名 | 変数名 |

このように考えることで、名前解決で行われる処理は、__特に不思議なことを行っているわけではない__ ことが理解できるのではないでしょうか？

他にも、この例えと、名前空間とスコープには以下のような類似点があります。

| 例え | 名前空間 |
|:--|:--|
| 家族の名簿などの __名簿__ には、その名簿を __適用できる__ 人物の __範囲__ がある | 名前空間には __スコープ__ がある |
| 田中さんの家の「一郎」さんと、佐藤さんの家の「一郎」さんのように、__異なる「家族」__ に __同じ名前__ の人物がいても __区別__ できる  | __異なる関数__ であれば、__同じ名前__ の __ローカル変数__ を __異なる変数__ として __区別__ できる  |
| 「近所の知り合い」、「学校の友人」、「会社の同僚」など、__集団が異なれば__、その中に __同じ名前__ の人物がいても __区別__ できる  | __異なるモジュール__ であれば、__同じ名前__ の __グローバル変数__ を __異なる変数__ として __区別__ できる  |

なお、以前の記事でも述べましたが、身近なものを使った例えは、物事をわかりやすく説明できるという利点がありますが、例えと、例えられたものは __全く同じものではありません__。名前空間と上記の例えは似てはいますが、__全く同じ性質を持つわけではない__ 点に注意して下さい。

上記の例えは、名前解決で行われる処理が __理解できない人__ に、処理の __イメージを掴む__ ことが出来るようにするために紹介しました。イメージがつかめた方は、今一度、名前解決で行われる処理の手順を読み直して、__正しい知識を身に付ける__ ようにして下さい。

# 名前の種類を見分けやすいプログラムの記述方法

ここまでの説明を聞いても、名前がどの名前空間で管理されているかを __判定する手順__ が __複雑__ だと思っている人が多いかもしれません。

__他人が記述__ したプログラムを __理解__ する際は、どうしても __先ほどの手順__ で名前がどの名前空間で管理されているかを __判定する必要__ があります。一方、__自分でプログラムを記述する__ 場合には、名前がどの名前空間で管理されているかが、__見分けやすくなる__ ようなプログラムの __記述方法__ があります。

## 名前の種類

ここまでの記事では、「__変数名__」と「__関数名__」という 2 種類の名前について説明しました。

:::note
Python の名前には他にも「クラス名」と「モジュール名」があります。この 2 つについては、必要になった時点で説明することにしますので、今回の記事では取り上げません。
:::

また、名前空間には「__組み込み名前空間__」、「__グローバル名前空間__」、「__ローカル名前空間__」の __3 種類__ があります。

これらを組み合わせることで、名前を「__組み込み定数__」[^7]、「__組み込み関数__」、「__グローバル変数__」、「__グローバル関数__」、「__ローカル変数__」、「__ローカル関数__」の __6 種類に分類__ することができます。

この __6 種類の名前__ を __簡単に見分ける__ ことが出来るプログラムの __記述方法__ について説明します。

:::note
`if` や `for` などの __予約語__ はプログラムの中で __特別な意味__ を持つ __キーワード__ で、予約語を変数名などの __名前に使うことはできません__。また、予約語は変数名などのように、オブジェクトに対応づけられた __名前ではない__ ので、どの __名前空間__ にも __登録されません__。Python の予約語の一覧については下記のリンク先を参照して下さい。
:::

https://docs.python.org/ja/3/reference/lexical_analysis.html#keywords

## 組み込み定数

下記のリンク先の組み込み定数の一覧を見て下さい。

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

一覧からわかるように、組み込み定数の __数は少なく__、その中で __主に使われる__ のは、__論理型のデータ__ を表す __`True`__、__`False`__、と、__データが存在しないこと__ を表す __`None`__ の __3 つ__ です。

組み込み定数は、他の名前と異なり、__どの名前空間__ であっても、__代入処理__ を行うことは __できません__（__エラーが発生__ します）。従って、__これらの名前は__ どこに記述されていた場合でも __必ず組み込み定数__ です。

上記の __3 つ__ の組み込み定数を __覚えておけば__、__組み込み定数__ であるかどうかを __見分けることは容易__ です。また、この 3 つは __頻繁に使われる__ ものなので、嫌でも覚えることになるでしょう。

:::note warn
__`True`__、__`False`__、__`None`__ の 3 つは常に __組み込み定数__ である。
:::

## 組み込み関数

`print` や `id` などの __組み込み関数__ をプログラムで __利用する場合__ は、それらの名前が組み込み関数の名前であることを __理解しながら__ プログラムを __記述しているはず__ です。従って、自分が __良く使う__ 組み込み関数の名前がプログラムに記述されている場合は、その名前が組み込み関数の名前であることを __見分けることは難しくない__ と思います。

下記のリンク先の、__組み込み関数の一覧__ を見ればわかりますが、組み込み関数は __約 80__ もの種類があり、そのすべてを __覚えるのは大変__ です（筆者もすべてを暗記しているわけではありません）。

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

__出来る限り__、プログラムに記述する __名前__ は、__組み込み関数の名前と被らない__ ような名前に __するべき__ ですが、__意図せずに__ 組み込み関数と __同じ名前の変数に値を代入__ するようなプログラムを記述することがあるかもしれません。そのようなプログラムを記述して実行しても、__エラーは発生しません__。単に、その組み込み関数をプログラムで __使用できなくなる__ だけです。

例えば、下記のプログラムは、1 行目で `id` という __組み込み関数__ を `print` で表示しています。__組み込み関数を `print` で表示__ すると、下記の実行結果のように「__\<built-in function xxx\>__」（xxx の中には組み込み関数の名前が入ります）が表示されます。

その後の 2 行目で `id = 1` という代入処理を行うことにより、__`id` はグローバル変数__ になります。従って、3 行目で `print` で `id` を表示すると、`1` が表示されます。また、このプログラムは 2 行目以降では `id` という __組み込み関数を利用できなくなります__。

```python
print(id)
id = 1
print(id)
```
実行結果
```
<built-in function id>
1
```

上記のような現象が起きるのは、2 行目の `id = 1` によって、__グローバル名前空間__ に、__`id` という名前__ から `1` を管理するオブジェクトへの __対応づけが登録__ されるためです。3 行目の `id` の名前解決を行うと、__グローバル名前空間__ に `id` という名前が __登録されている__ ため、`1` を管理するオブジェクトに対応づけられます。

プログラムを __記述した本人__ にとっては、組み込み関数と同じ名前の変数に値を代入した際の __デメリット__ は、その組み込み関数を利用できなくなる __だけ__ のように __思えるかもしれません__ が、実際には下記のような __デメリット__ があるため、組み込み関数と同じ名前の変数に値を代入することは __避けたほうが良い__ でしょう。

* 最初は使わないと思っていた組み込み関数を、__後で使いたくなった時に困る__ ことになる
* __他人が__ そのプログラムを __見たとき__ に、プログラムの __意味が非常に分かりづらくなる__

なお、自分のプログラムで __使いたくなるような名前__ の組み込み関数は __あまり多くない__ ので、そのような名前に __絞って__ 組み込み関数の __名前を覚えておけば良い__ のではないかと思います。

個人的には、__`all`__、__`any`__、__`id`__、__`input`__、__`len`__、__`list`__、__`max`__、__`min`__、__`set`__、__`sum`__、__`type`__ あたりの名前が __間違って変数名や関数名に使いそうな名前__ だと思いますので、それらの名前を覚えておいて、__値を代入しないように気を付けて下さい__。人によっては他の名前も使いたくなる可能性があるので、先ほどのリンク先の組み込み関数の名前の一覧を __ざっと眺めて確認しておく__ ことをお勧めします。なお、その際に組み込み関数の処理の内容までを覚える必要はありません。

:::note warn
間違って値を代入しそうな組み込み関数の __名前を憶えておき__、その名前に __値を代入__ したり、その名前の __関数を定義__ したり __しない__ ようにすることで、名前が __組み込み関数であるかどうかを見分ける__ ことが出来る。
:::

:::note
上記の説明を記述している時に気づいたのですが、筆者も「その 13」と「その 14」の記事で、うっかり組み込み関数 __`sum`__ と同じ名前の関数を定義していました。それらの記事を訂正しても良いのですが、__うっかり__ 組み込み関数と __同じ名前を使ってしまう実例__ としてあえてそのまま残しておくことにします。
:::

:::note
組み込み関数の名前に値を代入した後でも、その組み込み関数を利用する方法があります。

組み込み関数は、__ビルトインモジュール内で定義__ されており、__`import builtins`__ と記述することで、ビルトインモジュールを __インポート__ することが出来ます。

インポートしたモジュール内で定義された関数は、__`モジュールの名前.関数名`__ と記述することで利用することが出来ます。

下記のプログラムは `id` に 1 を代入した後で、__`builtins.id`__ を記述することで、組み込み関数 `id` を `print` で表示しています。

```python
import builtins

id = 1
print(builtins.id)
```
実行結果
```
<built-in function id>
```

なお、モジュールのインポートの方法と、モジュールの中で定義された名前の利用方法については、必要になった時に詳しく説明します。
:::

## ローカル関数

これまで紹介したプログラムでそのような記述を行ったことはありませんが、__関数のブロックの中__ に __関数の定義を記述__ することが出来ます。関数のブロックの中で __代入処理__ を行うと、代入先の変数が __ローカル変数__ になるのと __同様__ に、__関数のブロックの中で定義__ された関数は __ローカル関数__ になります。以前の記事で説明した「__関数の定義__ を実行すると、関数と __同じ名前の変数__ に、関数の定義を表す __データを代入__ するという処理が行われる」というこをと思い出してください。

__ローカル関数__ は、__ローカル変数と同様__ に、その __関数のブロックの中だけでしか利用できません__。

下記のプログラムは、関数 `printA` の __ブロックの中__ で、__関数 `printB` を定義__ しています。

```python
1  def printA():
2      print("A")
3      def printB():
4          print("B")
5      printB()
6
7  printA()
8  printB()
```

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

```diff_python
def printA():
    print("A")
    def printB():
        print("B")
    printB()

printA()
printB()
```
</details>

実行結果
```
A
B
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[3], line 8
      5     printB()
      7 printA()
----> 8 printB()

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

__3、4 行目で定義__ されている __`printB`__ は、`printA` の __ローカル関数__ なので、`printA` の __ブロックの中__ である 5 行目から __呼び出すことが出来ます__。実行結果の 2 行目に表示される `B` は、5 行目から呼び出された `printB` によって表示されたものです。

しかし、`printA` の __ブロックの外__ の 8 行目では、`printB` の __名前解決を行うことが出来ない__ ので、8 行目を実行すると __NameError__ という __エラーが発生__ します。
 
ローカル関数は __特別な目的__ で記述するものなので、一般的には記述することは __あまりありません__。従って、当面の間は、上記のプログラムの意味がわからなくても気にする必要はないでしょう。ローカル関数に関しては必要になった時点で説明します。

ただし、__インデントを間違える__ などの理由で、__間違って__ 関数のブロックの中で関数の定義を記述してしまうことが原因で __エラーが発生する__ 場合があるので、その点には注意して下さい。

本記事でも、__必要がない限り__ は __ローカル関数の定義は行いません__。また、今回の記事では、以降の内容は、__ローカル関数をプログラムに記述しない__ ものとして説明を行います。

:::note warn
__関数のブロックの中__ で関数を __定義しないようにする__ ことで、__ローカル関数__ のことを __気にする必要がなくなる__。
:::

## グローバル関数

上記で説明した __ローカル関数を__ プログラムの中に __記述しなければ__、組み込み関数 __以外の全ての関数__ は、__グローバル関数__ になります。__本記事__ でも特別な場合を除いて、__グローバル関数のみを定義__ します[^8]。また、これまでの記事でローカル関数を定義したことは __一度もありません__。

関数は __何らかの処理を行う__ ものなので、__関数名__ を `place_mark` のように、英語の __動詞を含めた名前__ にすることで、__見ただけで__ 関数名であるかどうかを __判別できる__ ように __工夫する__ ことが出来ます。

また、関数名は主に __関数の定義__ と __関数呼び出し__ の際に記述されます。関数の定義の場合は、__関数名の直前に `def` が記述__ され、関数呼び出しの場合は、__関数名の直後に `(` が記述__ されるので、何れの場合も、関数の名前であるかどうかは容易に区別することが出来ます。

:::note
__VSCode__ で関数を定義した場合は、その __関数名の上にマウスカーソルを移動__ すると、関数を表す英単語である __「function」が記述されたメッセージが表示__ されるので、これを見て関数であるかどうかを __確認することも出来ます__。

下図は、下から 2 行目の `assign_one` の上にマウスカーソルを移動した場合の図で、`assign_one` のすぐ上に、function が記述されたメッセージ[^9]が表示されています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/9a66632d-94c9-3929-0749-db9916c6fa6d.png" width="300">
:::

:::note warn
「__プログラムの中にローカル関数を記述しない__」、「__関数の名前を工夫する__」の 2 点を守ることによって、名前が __グローバル関数__ であることを簡単に __見分ける__ ことが出来る。
:::

## グローバル変数とローカル変数

ここまでで、組み込み定数、組み込み関数、グローバル関数、ローカル関数の見分け方について説明しました。次に、それ __以外の名前__ である、__グローバル変数とローカル変数__ の見分け方について説明します。

### グローバル変数とローカル変数の見分け方

__代入文__ では、__代入先の変数__ は、代入文をスコープとする __最も内側の名前空間__ に名前が登録される[^2]ので、以下のように、グローバル変数とローカル変数を __簡単に見分ける__ ことが出来ます。

* __関数のブロックの外__ に記述された __代入文__ の場合は、__グローバル変数__ である
* 関数の __仮引数__ と、__関数のブロックの中__ に記述された __代入文__ の場合は、__ローカル変数__ である

また、__関数のブロックの外__ に記述された __式の中の変数__ は、__グローバル変数__ です。

ここまでは __簡単に見分けることが出来ます__ が、問題は __関数のブロックの中__ の __式__ に記述された __変数__ で、今回の記事の最初で紹介した __少し複雑な手順__ に従って、その名前がグローバル変数かローカル変数かを __見分ける必要__ があります。その際に、__式__ の中に __記述された名前だけ__ を見てその __判定を行うことが出来ない__ 事が __複雑さの原因__ になっています。

この __複雑さを解消__ するプログラムの __記述方法__ について説明します。

### 関数のブロックの中の変数を見分けやすいプログラムの記述方法

問題を複雑にしているのは、関数の __ブロックの中__ にグローバル変数とローカル変数が __混在する__ 点なので、関数の __ブロックの中__ に __ローカル変数のみを記述する__ ようすることで、この __複雑さを解消__ することが出来ます。

そのようなプログラムは、以下の __条件を守る__ ことで記述することが出来ます。

:::note warn
関数の __ブロックの中の変数__ は、__仮引数__ か、その関数の __ブロックの中で値を代入した変数のみ__ を記述する。
:::

上記の条件を守って関数の定義を記述すると、__代入文と式__ の __区別をすることなく__ グローバル変数と、ローカル変数を __関数のブロックの内外__ で完全に __分離する__ ことが出来ます。その結果、ローカル変数とグローバル変数を以下のように簡単に見分けることが出来るようになります。

:::note warn
グローバル変数とローカル変数の見分け方。

* __関数の外__ に記述した変数は、__すべてグローバル変数__ である
* __関数のブロックの中__ に記述した変数は、__すべてローカル変数__ である
:::

図で示すと、下図の __オレンジ色の範囲__ に記述された変数が __ローカル変数__ 、__オレンジ色の外__ に記述された変数が __グローバル変数__ になります。

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/9449049f-3a3b-6c86-351d-83ea36cd3d6e.png)

:::note
上図は、「その 14」の記事で使った図を再掲したものです。そのため、上図のプログラムは、上記の条件に従ってプログラムが __記述されていない__ ので、オレンジ色の枠の中にグローバル変数が記述されています。実際に `place_mark` の関数ブロック内に記述されている `board` はグローバル変数です。
:::

## グローバル変数とローカル変数を分離するメリット

グローバル変数と、ローカル変数を __関数のブロックの内外で分離__ するということは、関数の __入力__ である __実引数__ と、__出力__ である __返り値__ 以外の手段で、関数のブロックの内外の __データの受け渡し__ を行うことが __出来ない__ という事です。このことには、変数を見分ける __以外__ にも __大きなメリット__ があります。

### 関数の中へのデータの受け渡しを実引数に限定するメリット

関数の __外部から__ 関数の __中へ__ の __データの受け渡し__ を __実引数に限定__ することで、その関数のブロックの中で行われる __処理の結果__ や __出力__ は、__実引数__ によって関数に入力されたデータ __のみ__ によって __定まる__ ようになります。別の言葉でいうと、__実引数以外の要因__ で、関数の __処理の結果__ や __出力__ が __変化しない__ ということです。そのような関数は、__入力__ と __処理の結果__ や __出力__ の __因果関係__ が __明確__ になるというメリットがあります。

:::note
ほとんどの物事には __例外__ があります。例えば、関数のブロックの中で、キーボードからの入力に応じた処理を行う場合、乱数（サイコロの出目のようなでたらめな数のこと）を使って計算を行う場合、現在の時刻を表示する場合などでは、実引数以外の要因で処理の結果が変化します。

今回の記事の説明は、関数のブロックの中で、そのような処理を行わない関数についての説明だと思ってください。
:::

一般的に関数は、__入力__ された __データに応じた__ 処理と出力を行います。入力と __無関係な要因__ で処理と出力が __変化する__ 関数は __使いづらい__ と思いませんか？

分かりづらいと思いますので、具体例を挙げます。下記のプログラムは、2 つの実引数の値の合計を計算し、その計算結果を返り値として返す `add` という名前の関数を定義しています。この関数を、__同じ__ 実引数の __組み合わせ__ を記述して呼び出した場合は、関数のブロックの中で __同じ処理__ が行われ、__同じ値__ を返り値として __返します__。例えば、`add(1, 2)` の返り値は、どんな場合でも __必ず `3`__ になります。

```python
def add(x, y):
    return x + y   # この x と y はローカル変数

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

一方、下記のプログラムで定義された `add_y` という名前の関数は、__実引数__ の値と __グローバル変数 `y`__ の値の合計を計算し、その計算結果を返り値として返す関数です。この `add_y` でも、下記のプログラムの 4、5 行目のように、グローバル変数 `y` に `2` を代入してから `add_y(1)` を呼び出すことで、`add(1, 2)` と同様に `1 + 2` の計算が行われ、`3` が返り値として返ります。

しかし、`add_y(1)` の返り値は、下記の 6、7 行目の実行結果のように、__実引数以外__ の __グローバル変数 `y` の値__ によって __変化__ します。

```python 
1  def add_y(x):
2      return x + y   # x はローカル変数だが、y はグローバル変数
3
4  y = 2
5  print(add_y(1))
6  y = 5
7  print(add_y(1))
```

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

```diff_python
def add_y(x):
    return x + y   # x はローカル変数だが、y はグローバル変数

y = 2
print(add_y(1))
y = 5
print(add_y(1))
```
</details>

実行結果
```
3
6
```

このような、__実引数と関係のないグローバル変数の値__ によって、__処理の結果__ や __出力__ が変わるような関数は、その関数を __利用する側の立場__ から見ると、__入力__ と __処理の結果__ や __出力__ の __因果関係がわからなくなる__ ため、関数が __扱いづらくなる__ という大きな __デメリット__ があります。

現実の例で例えると、ボタンによって __砂糖の量__ と、__氷の量__ を __調整__ することができるコーヒーの自動販売機を思い浮かべて下さい。この自動販売機の __入力__ は、押したボタンによって決まる「__砂糖の量__」と「__氷の量__」で、__処理__ は「__コーヒーと砂糖と氷を混ぜること__」、__出力__ は「__混ぜ合わせたコーヒー__」です。

この自動販売機を利用する人は、砂糖の量と氷の量のボタンを __同じ組み合わせで押した場合__ は、__毎回同じ__ 分量の砂糖と氷が入った __コーヒー__ が出てくることを __期待する__ はずです。

この自動販売機が、ボタン以外の、例えば周囲の気温などの __利用者が知らない要因__ で、出てくるコーヒーの __銘柄が変わってしまう__ 場合、そのような自動販売機は使いづらいと思いませんか？

:::note warn
関数の __ブロックの中で利用__ する、__関数の外部のデータ__ は、__特別な理由__ がある場合を __除いて__、__実引数__ によって __関数の仮引数に渡されたデータのみ__ を使ったほうが良い。
:::

本記事でも、特別な理由がない限り、関数をそのように定義する事にします。

### 関数の外へのデータの受け渡しを返り値に限定するメリット

関数は、「__入力__ するデータ」、「行われる __処理__」、「__出力__ されるデータ」について __理解__ していれば、具体的に関数の中でどのような処理が行われているかを __知らなくても利用できる__ という利点があります。例えば、これまで何度も利用してきた `print` という組み込み関数は、「__実引数__ で __入力__ したデータを、__表示する__ という __処理__」[^10]を行う関数ですが、この関数が具体的にどのような処理によって表示を行っているかについて __知る必要はありません__。

`print` のような、__他人が定義__ した関数を、関数の定義の __プログラムを見ずに__ 利用する場合は、関数の処理の中で、__グローバル変数に値を代入する__ ような処理が行われていたとしても、__そのことを知ることはできません__。他人が定義した関数を利用する際に、その関数が何らかの __グローバル変数に値を代入する処理__ を内部で行っていた場合[^11]、その __グローバル変数と同じ名前__ を使ったプログラムの __動作がおかしなこと__ になってしまう可能性があります。

:::note warn
__関数の外へのデータの受け渡し__ を __返り値に限定__ することで、__関数呼び出しの処理__ によって、__実引数と無関係__ な __グローバル変数__ に勝手に値が __代入されることがなくなります__。
:::

先程のコーヒーの自動販売機で例えると、関数の返り値に相当するのは、自動販売機の取り出し口に出てきたコーヒーです。__自分で決めたグローバル変数__ に関数の __返り値を代入__ することは、__取り出し口のコーヒー__ を、__購入者が決めた人物__ に __受け取らせる__ ことに相当します。

一方、関数のブロックの中でグローバル変数に値を代入する処理は、コーヒーの自動販売機でコーヒーを購入した際に、自動販売機が __勝手に__ 自動販売機の __外にいる__ 近くに歩いている人などの、__購入者が予期しない人__ にコーヒーを __勝手に渡す__ ことに相当します。そのような自動販売機を使いたいと思いますか？


### 関数の副作用

上記のノートの説明で、「__実引数と無関係__ なグローバル変数に勝手に値が代入されることがなくなります」のように表記したのには理由があります。これまでに説明した方法では、関数の __ブロックの中__ で __グローバル変数__ に __直接__ 値を __代入__ することは __できません__ が、例えば実引数が list の場合、その実引数の値を代入した __仮引数の要素に__ 値を代入することによって、__実引数に関連する__ グローバル変数の __値__ を __間接的__ に __変更する__ ことは __できる__ からです。

関数呼び出しの処理によって、関数のブロックの外をスコープとする __グローバル変数__ の値が __変更__ されることを、関数の __副作用__ と呼びます。今回の記事で紹介した名前の解決を見分けやすいプログラムの記述方法は、関数の副作用を無くすことは __できません__ が、関数の副作用を __制御する__ ことはできます。そのことと関数の副作用に関しては、次回の記事で詳しく説明します。

### 関数呼び出しを使ってグローバル変数に値を代入する例

グローバル変数と、ローカル変数を関数のブロックの内外で分離した場合に、関数呼び出しを使って、__グローバル変数に直接値を代入__ するためには、関数の __ブロックの外__ で、グローバル変数に __関数の返り値を代入__ する必要があります。

下記のプログラムは、過去の記事で関数の処理を説明する際に紹介したプログラムです。このプログラムは、5 行目のグローバル変数 `a` に代入された値が `assign_one` という関数で `1` に変更されると誤解されやすい例として紹介しました。2 行目の処理によって、5 行目のグローバル変数 `a` の値が `1` に変更されないのは、2 行目のローカル変数 `a` と、`5` 行目のグローバル変数 `a` が __同じ名前__ であっても __異なる変数__ だからです。

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

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

上記のプログラムを、下記のプログラムのように、4 行目で __ローカル変数 `a`__ を関数の __返り値とし__、7 行目で __グローバル変数 `a`__ に関数の __返り値を代入__ することで、ローカル変数 `a` の値である `1` を、グローバル変数 `a` に __受け渡す__ ことが出来ます。その結果、8 行目を実行すると `1` が表示されます。

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

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

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

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

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

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

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

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

## まとめ

:::note warn
以下の __条件を守って__ プログラムを記述することで、__名前の種類を簡単に見分ける__ ことが出来るようになる。

* 組み込み関数と __同じ名前を使わない__ ようにする
* 関数の __ブロックの中__ に __関数の定義を記述しない__ ようにする
* __関数の名前__ を `place_mark` のように、__動詞など__ を使って __動作を表すような名前__ にする
* 関数の __ブロックの中の変数__ は、__仮引数__ か __ブロックの中で値を代入した変数のみ__ を記述する

上記の条件を守ることで、名前を以下の手順で見分けることが出来る。

1. __`True`__、__`False`__、__`None`__ は  __組み込み定数__ である
2. __`id`__ など、変数の名前として __間違って使いそうな__ 組み込み関数の __名前を覚えておく__ ことで、__組み込み関数__ の名前を __見分ける__ ことが出来る
3. __動作を表すような名前__ は、__グローバル関数__ である
4. __関数の定義の外__ に記述された __変数__ は、__グローバル変数__ である
5. 関数の __仮引数__ と、関数の __ブロックの中__ に記述された __変数__ は、__ローカル変数__ である
:::

本記事でも、特別な場合を除いて上記の __条件を守ったプログラムを記述__ します。

:::note warn
「関数の __ブロックの中の変数__ は、__仮引数__ か、__ブロックの中で値を代入した変数のみ__ を記述する」という条件を守った場合、__関数の内外で__ ローカル変数とグローバル変数を __分離する__ ことができ、以下のようなメリットが得られる。

* __入力__ と __処理の結果__ や __出力__ の __因果関係__ が __明確__ になる
* __関数呼び出しの処理__ によって、__実引数と無関係__ な __グローバル変数__ に勝手に値が __代入されることがなくなる__
:::

# 次回の記事について

次回の記事では、`initialize_board` のバグの原因と修正方法などについて説明した後で、関数の副作用と、名前空間が必要になる理由について説明します。

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

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

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

# 次回の記事

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

# 更新履歴

| 更新日時 | 更新内容 |
|:--|:--|
| 2023/10/09 | `for y in range(3)` を `for x in range(3)` に修正しました |

[^1]:他にも、クラスの定義やモジュールのインポートの際などでも行われます。それらについては必要になった時点で説明します
[^2]:実際には、`global` を使うことで、関数のブロックの中でグローバル変数に値を代入することができるという __例外があります__ が、`global` は __頻繁に使われるものではない__ ので、今回の記事では `global` を __使わないという前提__ で話を進めます。`global` については次回の記事で詳しく説明します
[^3]:本記事で紹介したプログラムにはまだ出てきていませんが、関数のブロックの中に関数を定義した場合は、その関数はローカル関数になります。ローカル関数については、この後で説明します
[^4]:「野球選手の一郎」のように、名前を特定できるような情報を __含まず__ に、__名前だけ__ を出した場合のことです
[^5]:これは、対応づけの手順の一例で、もちろん、これ以外にも名前を対応づける手順はあるでしょう
[^6]:もちろん、人によっては、別の「一郎」という名前の有名人を思い浮かべることはあるでしょう
[^7]:組み込み定数は、変数のように __値を代入することが出来ない__ ので、組み込み変数ではなく、組み込み定数と呼ばれます
[^8]:まだ説明していない、クラスの定義の中に関数の定義を記述することは良くありますが、そのような関数は、メソッドという名前で一般的な関数と区別されます。メソッドについては必要になった時点で説明します
[^9]:表示される「(function) def assign_one(a: Any) -> None」の意味については、今後の記事で説明します
[^10]:`print` は __返り値を返さない__（厳密には None を返します）関数なので、出力はありません
[^11]:次回の記事で説明する、`global` を使うことで、関数のブロックの中でグローバル変数に値を代入することができます