Pythonで〇×ゲームのAIを一から作成する　その16　プログラムの修正と関数の副作用

# 目次と前回の記事

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

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

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

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

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

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

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

## 前回までのおさらい

「その 13」の記事では、「ゲーム盤の (x, y) のマスにマークを配置する」関数と、「ゲーム盤を初期化する」関数を定義し、それらの関数が正しく動作するかを確認した結果、バグがある事が判明しました。

「その 14」の記事ではバグが起きる原因を理解するために __必要な知識__ として、Python の __名前空間__ と __スコープ__ の仕組みについて説明しました。

前回の記事では、名前空間とスコープに関する処理のまとめと、名前解決を __見分けやすい__ プログラムの __記述方法__ について説明しました。

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


# 「その 13」のプログラムのバグ

下記に、「その 13」の記事の最後で紹介した、バグのあるプログラムを再掲します。下記のプログラムは、10 行目で `initialize_board()` を呼び出して、__`board`__ に __初期化されたゲーム盤を代入__ しているにも関わらず、11 行目で `place_mark(0, 1, "〇")` を呼び出して、(0, 1) のマスに 〇 を配置しようとすると、「( 0 , 1 ) のマスにはマークが配置済です」というメッセージが表示されるというバグがあります。

このことから、__`initialize_board()`__ の __関数呼び出しの処理__ に、__バグ__ がある可能性が高そうです。

```python
 1  def place_mark(x, y, mark):
 2      if board[x][y] == " ":
 3          board[x][y] = mark
 4      else:
 5          print("(", x, ",", y, ") のマスにはマークが配置済です")
 6
 7  def initialize_board():
 8      board = [[" "] * 3 for x in range(3)]
 9
10  initialize_board()      # ゲーム盤の初期化処理を行う関数を呼び出す
11  place_mark(0, 1, "〇")  # (0, 1) のマスに 〇 を配置する
12  print(board)
13  place_mark(0, 1, "×")   # (0, 1) のマスに × を配置する
14  print(board)
```

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

```diff_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)
```
</details>

実行結果
```
( 0 , 1 ) のマスにはマークが配置済です
[[' ', '〇', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
( 0 , 1 ) のマスにはマークが配置済です
[[' ', '〇', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
```

実は、このプログラムを __新しい__ marubatsu.ipynb に __記述して実行__ するか、JupyterLab の上部の「__再起動__」ボタンを __クリックしてから実行__ すると、上記のような実行結果にはならず、下記の実行結果のように __エラーが発生__ します。先に、下記のエラーについて説明します。

実行結果
```
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\016\marubatsu.ipynb セル 1 line 1
      8     board = [[" "] * 3 for x in range(3)]
     10 initialize_board()    # ゲーム盤の初期化処理を行う関数を呼び出す
---> 11 place_mark(0, 1, "〇")  # (0, 1) のマスに 〇 を配置する
     12 print(board)
     13 place_mark(0, 1, "×")   # (0, 1) のマスに × を配置する

c:\Users\ys\ai\marubatsu\016\marubatsu.ipynb セル 1 line 2
      1 def place_mark(x, y, mark):
----> 2     if board[x][y] == " ":
      3         board[x][y] = mark
      4     else:

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

上記の実行結果には、上下に 2 つのプログラムが表示されていますが、これは、上の `---->` が表示された 11 行目の `place_mark(0, 1, "〇")` の __関数呼び出しによって__、下に記述されている __`place_mark` の関数が呼び出され__、その関数のブロックの `---->` が表示されている、プログラムの __2 行目__ の `if board[x][y] == " ":` で、「NameError: name 'board' is not defined」 という __エラーが発生している__ という意味を表します。

__同じ関数__ に対する __関数呼び出し__ の処理が、プログラムの __様々な場所に記述__ されることが __良くあります__。そのため、__関数呼び出しの中__ でエラーが発生した場合は、その関数呼び出しがプログラムの __どこから呼び出されたか__ の情報が __重要になる場合が多い__ ので、上記のように、__関数呼び出しが行われた行も併せて__ エラーメッセージが表示されます。

このエラーメッセージは、__`board`__ という __名前__（name）が __定義されていない__（is not defined）という意味の NameError （名前に関する）エラーで、__`board`__ という __名前__ が、プログラムの 2 行目をスコープとする __どの名前空間にも存在しない__ ことが原因です。

下記の表は、このプログラムの処理を順を追って説明したものです。

| 行数 | 行われる処理 |
|:--|:--|
| 10、7  | `initialize_board()` によって、関数呼び出しが行われる。実引数と仮引数は存在しないので仮引数に実引数を代入する処理は行われない  |
| 8  | `board` に初期化されたゲーム盤が代入される。この `board` は __`initialize_board` のローカル変数__ なので、この __関数の中でのみ__ 利用できる。関数呼び出しの処理が終了し、ローカル名前空間が削除され、次の 11 行目に処理が移る  |
| 11、1  | `place_mark(0, 1, "〇")` によって、関数呼び出しが行われる。仮引数に実引数を代入する処理が行われるが、エラーの原因には関係がないので説明は省略する  |
| 2  | `if board[x][y] == " ":` の処理で、`board` という名前が記述されているので、__名前解決__ を行う必要がある。__`place_mark`__ の __ローカル名前空間__ には `board` という名前は __存在しない__。また、グローバル名前空間にも、組み込み名前空間にも  `board` という __名前は存在しない__。そのため、NameError という __エラーが発生__ する。なお、8 行目の `board` を管理していた `initialize_board` のローカル名前空間は、`initialize_board` の関数呼び出しが終了した時点で __削除されいる__ ので __利用することはできない__ |

名前空間についての正しい知識がない人は、`initialize_board` の中で、グローバル変数の `board` に初期化されたゲーム盤を代入しているように __勘違いする__ かもしれませんが、実際には、8 行目では  `initialize_board` の __ブロックの中でしか利用できない__、__ローカル変数の `board`__ に初期化されたゲーム盤を代入しています。そのため、2 行目の `if board[x][y] == " ":` をスコープとする __どの名前空間にも__、`board` という名前が __登録されていない__ 事がエラーの原因です。

## 「その 13」の記事で NameError が発生しない理由

「その 13」の記事でこのプログラムを実行した際に、NameError が発生しなかった理由は、このプログラムを __実行する前__ に、「その 13」の記事の中ほどで __下記のプログラムを実行していた__ からです。

```python
board = [[" "] * 3 for x in range(3)]
if board[0][1] == " ":
    board[0][1] = "〇"
else:
    print("(0, 1) のマスにはマークが配置済です")
```

上記のプログラムを実行した結果、__グローバル変数の `board`__ の値は、`[[' ', '〇', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]` というデータになります。その状態で、先程のプログラムを実行すると、2 行目の `if board[x][y] == " ":` をスコープとするグローバル名前空間に、上記のデータを管理するオブジェクトに対応づけられた __`board` が登録されている__ ので、エラーは発生しません。ただし、__`board[0][1]` の値は `"〇"`__ なので、この if 文の __条件式の計算結果は `False`__ になります。そのため、「(0, 1) のマスにはマークが配置済です」というメッセージが表示されます。

## プログラムの修正方法

先程のプログラムは、`initialize_board` のブロックの中で、__ローカル変数である `board` に対して__ 初期化されたゲーム盤を __代入する__ 処理を行っていた点が __バグの原因__ でした。

今回の記事では、この問題を解決する 2 種類の方法を紹介します。

### 初期化されたゲーム盤のデータを関数の返り値として返す方法

一つ目は、`initilaize_board` で行う処理を、下記のプログラムの 8 行目のように、初期化されたゲーム盤のデータを __関数の返り値として返す__ ように修正し、10 行目のように、 __関数のブロックの外__ で __グローバル変数 `board`__ に関数の __返り値を代入する__ ように修正するという方法です。

下記のプログラムを、JupyterLab の __「再起動」ボタンを押してから実行__ しても、__エラーは発生しません__。また、実行結果から、11 行目の `place_mark` によってマークが配置済であるという __メッセージが表示されなくなった__ ことを確認することが出来ます。

```python
 1  def place_mark(x, y, mark):
 2      if board[x][y] == " ":
 3          board[x][y] = mark
 4      else:
 5          print("(", x, ",", y, ") のマスにはマークが配置済です")
 6
 7  def initialize_board():
 8      return [[" "] * 3 for x in range(3)]
 9
10  board = initialize_board()    # 初期化されたゲーム盤のデータを返す関数を呼び出す
11  place_mark(0, 1, "〇")        # (0, 1) のマスに 〇 を配置する
12  print(board)
13  place_mark(0, 1, "×")         # (0, 1) のマスに × を配置する
14  print(board)
```

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

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

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

board = initialize_board()    # 初期化されたゲーム盤のデータを返す関数を呼び出す
place_mark(0, 1, "〇")        # (0, 1) のマスに 〇 を配置する
print(board)
place_mark(0, 1, "×")         # (0, 1) のマスに × を配置する
print(board)
```
</details>

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

```diff_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)]
+    return [[" "] * 3 for x in range(3)]

- initialize_board()  # 初期化されたゲーム盤のデータを返す関数を呼び出す
+ board = initialize_board()  # 初期化されたゲーム盤のデータを返す関数を呼び出す
place_mark(0, 1, "〇")        # (0, 1) のマスに 〇 を配置する
print(board)
place_mark(0, 1, "×")         # (0, 1) のマスに × を配置する
print(board)
```
</details>

実行結果
```
[[' ', '〇', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
( 0 , 1 ) のマスにはマークが配置済です
[[' ', '〇', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
```

上記の、__関数の返り値をグローバル変数に代入する方法__ については、__前回の記事の最後__ で紹介しましたので、忘れた方は前回の記事を復習して下さい。

#### 上記のプログラムに関する補足

上記の修正したプログラムを見た方の中で、`initialize_board` を以下のような処理を行うように定義したほうが良いと思った方はいないでしょうか？

* __仮引数 `board`__ で、__グローバル変数 `board`__ の値を __受け取る__
* `initialize_board` の __ブロックの中__ で、__ローカル変数 `board`__ に対して初期化されたゲーム盤を __代入する__
* __ローカル変数 `board`__ を関数の __返り値として返す__ ようにし、__グローバル変数 `board`__ にその __返り値を代入する__

下記はそのように `initialize_board` を定義したプログラムです。しかし、実際にこのプログラムを、JupyterLab の __「再起動」ボタンを押してから実行__ すると、下記のようなエラーが発生してしまいます。

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

board = initialize_board(board)
```
実行結果
```
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\016\marubatsu.ipynb セル 4 line 5
      2     board = [[" "] * 3 for x in range(3)]
      3     return board
----> 5 board = initialize_board(board)

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

このエラーは、__5 行目__ の `initialize_board(board)` の __関数呼び出しに原因__ があります。この関数呼び出しには、__`board` という名前__ が記述されているので、__名前解決を行う必要__ がありますが、この時点では __`board` というグローバル変数に値が代入されていません__。そのため、__グローバル名前空間__ に `board` という __名前が登録されていない__ ため、__NameError が発生__ します。

:::note
上記のプログラムを __実行する前__ に、__「再起動」ボタンを押した__ のは、今回の記事では、上記のプログラムを __実行する前に__、`board = initialize_board()` を __実行している__ ため、グローバル名前空間に `board` という __名前が登録されている__ ためです。

「再起動」ボタンを押してから上記のプログラムを実行することにより、Python のプログラムが __新しく実行__ され、__グローバル名前空間__ が __空の状態__ で新しく __作り直されます__。
:::

また、仮にグローバル変数 `board` に __値が代入されていたとしても__、`initialize_board` の __仮引数__ に `board` は __必要ありません__。その理由は、`initialize_board` の 2 行目で、ローカル変数である仮引数 `board` に __代入する式の中__ に、__ローカル変数 `board` の値が使われていない__ からです。別の言葉で説明すると、関数が行う処理の中で、関数に入力した __仮引数のデータを使うことがない__ のであれば、その __入力データ__ はそもそも __必要がない__ ということです。

分かりづらいと思いますので、プログラムの具体例を挙げます。下記のプログラムの 1、2 行目の `1` という数字を表示する `print_one` という関数に、__仮引数 `a` が必要だと思いますか__？

この関数では、__仮引数 `a` の値__ は、関数の __ブロック内で一度も使われていません__。従って、関数呼び出しの際に、__どのような値を実引数に記述__ したとしても、この関数の処理に __影響は与えません__。つまり、__仮引数 `a`__ はこの関数には __不要である__ ということです。

```python
def print_one(a):
    print(1)
```

下記のプログラムは、2 行目で仮引数 `a` に `1` を代入し、3 行目で `a` の値を表示しているので上記のプログラムと同様に、__必ず `1` が表示__ されます。2 行目で行われる処理は、ローカル変数である仮引数に `a` にどのような値が代入されていたとしても、__その値を利用することなく__ `a` に `1`を __代入して上書きする__ 処理を行っています。従って、関数呼び出しの際に、__どのような値を実引数に記述__ したとしても、この関数の処理に __影響は与えません__。つまり、__仮引数 `a`__ はこの関数には __不要である__ ということです。


```python
def print_one(a):
    a = 1
    print(a)
```

上記の `print_one` と先ほどの `initialize_board` は、どちらも __仮引数__ に、__仮引数の値を使わない式を代入__ しています。従って、`initialize_board` の __仮引数は不要です__。

#### 関数の返り値のローカル変数への代入

`initialize_board` を下記のように、初期化されたゲーム盤のデータを一旦 __ローカル変数 `board` に代入__ してから、その __`board` を返り値として返す__ ように定義する事もできます。

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

:::note
上記のプログラムは、先ほどの補足で紹介したバグのあるプログラムに __似ているように見える__ かもしれませんが、__仮引数を持たない__ 点が異なりまります。
:::

主に、以下のような場合に関数の返り値を __ローカル変数__ に一旦 __代入__ してから、`return ローカル変数` を記述して返り値を返します。

* 関数の返り値を計算するために、__複数の文を記述__ する必要がある場合
* __分かりやすい名前__ のローカル変数に関数の返り値の値を代入することによって、関数がどのようなデータを返り値として返すかを __わかりやすく示したい__ 場合

上記のプログラムは、返り値を 1 行の式で計算できるため、上記の 1 つ目の条件は満たしませんが、`board` という名前のローカル変数に返り値の値を代入することで、この関数がゲーム盤（board）のデータを返していることが __return 文を見ただけで分かるよう__ になります。

返り値をローカル変数に代入する場合も、そうでない場合も、__どちらも間違ってはいない__ ので、自分が __分かりやすいと思った方法で__ プログラムを記述すると良いでしょう。

本記事の `initialize_board` の定義は、返り値をローカル変数に代入しない方法を採用します。

### global を使う方法

もう 1 つの解決方法は、関数の __ブロックの中__ で __グローバル変数に対して直接代入処理を行う__ というものです。__関数のブロックの中__ に下記のように記述することで、その関数のブロック内では、__`global` の後に記述した変数__ を、__グローバル変数とみなして処理が行われる__ ようになります[^1]。

```python
global 変数名
```
:::note
関数のブロックの中に `global 変数名` を記述すると、その名前はその関数の __ブロック内__ では、ローカル変数として __使用することができなくなります__。例えば、`global 変数名` で指定した変数名を、ローカル変数である __仮引数の名前に使用__ することは __できません__。

下記のプログラムはその具体例です。

```python
def a(x):
    global x
```
実行結果
```
  Cell In[5], line 2
    global x
    ^
SyntaxError: name 'x' is parameter and global
```

上記のエラーメッセージは、以下のような意味を持ちます。

* __SyntaxError__
構文（文法のこと）（syntax）に関するエラー
* __invalid syntax__
`x` という名前（name）は、（ローカルな）仮引数（parameter）であり、なおかつ（and）グローバル（global）でもある。これは、`x` が __同時に__ ローカルとグローバルな __名前__ であることが __矛盾している__ ということを表すメッセージです
:::

下記が `global` を使ってバグを修正したプログラムです。修正前のプログラムとの違いは、8 行目に __`global board` が追加__ されている点のみです。その結果、9 行目の `board` は __グローバル変数の `board`__ に初期化されたゲーム盤を代入する処理になるので、この関数に return 文を記述するように __修正する必要も__、11 行目を `board = initialize_board()` のように __修正する必要もありません__。

下記の実行結果は、JupyterLab で __「再起動」ボタンをクリックしてから__ プログラムを実行した場合のものです。実行結果から、上記のプログラムで __エラーが発生しなくなった__ ことを確認することができます。また、12 行目の `place_mark` によってマークが配置済であるという メッセージが表示されなくなった ことを確認することが出来ます。

```python
 1  def place_mark(x, y, mark):
 2      if board[x][y] == " ":
 3          board[x][y] = mark
 4      else:
 5          print("(", x, ",", y, ") のマスにはマークが配置済です")
 6
 7  def initialize_board():
 8      global board
 9      board = [[" "] * 3 for x in range(3)]
10
11  initialize_board()    # ゲーム盤の初期化処理を行う関数を呼び出す
12  place_mark(0, 1, "〇")  # (0, 1) のマスに 〇 を配置する
13  print(board)
14  place_mark(0, 1, "×")   # (0, 1) のマスに × を配置する
15  print(board)
```

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

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

def initialize_board():
    global 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)
```
</details>

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

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

def initialize_board():
+    global 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)
```
</details>

実行結果
```
[[' ', '〇', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
( 0 , 1 ) のマスにはマークが配置済です
[[' ', '〇', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
```

#### global を使用する際の注意点

`global` を使用すると、__関数のブロックの中__ に、ローカル変数とグローバル変数が __混在するようになる__ ので、前回の記事で説明した __名前解決を見分けやすい__ プログラムの記述方法 __ではなくなる__、関数で行われる処理が __わかりづらくなる__ などの __欠点__ が発生します。そのため、__`global` の使用__ は一般的には __推奨されません__。

ただし、実際には `global` を __使ったほうが良い__ 場合や、`global` を __使わざるを得ない__ 場合があるので説明しました。本記事では、必要がない限りは、__`global` を使わない__ ことにし、使う必要がある場合はその理由を説明した上で使うことにします。

# `place_mark` の修正

`place_mark` の __ブロックの中__ の `board` は、__グローバル変数__ なので、`place_mark` のブロックの中にローカル変数とグローバル変数が __混在しています__。そこで、下記のプログラムのように、`place_mark` にゲーム盤のデータを代入する __仮引数 `board` を追加__ することで、関数のブロックの中に __ローカル変数のみが存在__ するように修正することにします。

```python
 1  def place_mark(board, x, y, mark):
 2      if board[x][y] == " ":
 3          board[x][y] = mark
 4      else:
 5          print("(", x, ",", y, ") のマスにはマークが配置済です")
 6
 7   def initialize_board():
 8      return [[" "] * 3 for x in range(3)]
 9
10  board = initialize_board()    # 初期化されたゲーム盤のデータを返す関数を呼び出す
11  place_mark(board, 0, 1, "〇")   # (0, 1) のマスに 〇 を配置する
12  print(board)
13  place_mark(board, 0, 1, "×")    # (0, 1) のマスに × を配置する
14  print(board)
```

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

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

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

board = initialize_board()      # 初期化されたゲーム盤のデータを返す関数を呼び出す
place_mark(board, 0, 1, "〇")   # (0, 1) のマスに 〇 を配置する
print(board)
place_mark(board, 0, 1, "×")    # (0, 1) のマスに × を配置する
print(board)
```
</details>

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

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

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

board = initialize_board()       # 初期化されたゲーム盤のデータを返す関数を呼び出す
- place_mark(0, 1, "〇")         # (0, 1) のマスに 〇 を配置する
+ place_mark(board, 0, 1, "〇")  # (0, 1) のマスに 〇 を配置する
print(board)
- place_mark(0, 1, "×")          # (0, 1) のマスに × を配置する
+ place_mark(board, 0, 1, "×")   # (0, 1) のマスに × を配置する
print(board)
```
</details>

実行結果
```
[[' ', '〇', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
( 0 , 1 ) のマスにはマークが配置済です
[[' ', '〇', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
```

上記のプログラムの修正箇所と修正内容は以下の通りです

| 行数 | 修正内容 |
|:--|:--|
| 1  | 仮引数の先頭に __`board` を追加__ することで、ブロック内の __`board` がローカル変数__ になるようにする  |
| 11、13  |  __`place_mark`__ の関数呼び出しの先頭の __実引数__ に __グローバル変数 `board`__ を記述することで、__仮引数__ `board` に __初期化されたゲーム盤を代入__ するようにする |

実行結果から、上記のプログラムが正しく動作していることを確認することが出来ます。

## 修正後の `place_mark` の利点

修正後の `place_mark` には __大きな利点__ があります。修正前のプログラムは、ゲーム盤のデータを __`board` という名前__ の __1 つの__ グローバル変数 __でしか扱うことが出来ません__ が、修正後のプログラムでは、__任意の数の__ ゲーム盤のデータを、__任意の名前の変数__ に代入することが出来ます。

下記のプログラムは、`board1` と `board2` という変数に、それぞれ __別の__ 初期化されたゲーム盤を __代入__ し、`board1` には (0, 1) のマスに 〇 を、`board2` には (1, 0) のマスに `×` を配置しています。実行結果からわかるように、`board1` と `board2` は __別々のゲーム盤を表している__ ことがわかります。

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

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

board1 = initialize_board()     # board1 に初期化されたゲーム盤を代入する
place_mark(board1, 0, 1, "〇")  # board1 の (0, 1) のマスに 〇 を配置する
print(board1)

board2 = initialize_board()     # board2 に初期化されたゲーム盤を代入する
place_mark(board2, 1, 0, "×")   # board2 の (1, 0) のマスに × を配置する
print(board2)
```
実行結果
```
[[' ', '〇', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
[[' ', ' ', ' '], ['×', ' ', ' '], [' ', ' ', ' ']]
```

## `place_mark` の修正に関する補足

`place_mark` が、ローカル変数 `board` を関数の __返り値として返していない__ 点が __気になる人__ はいないでしょうか？そのような人は、`place_mark` を下記のプログラムのように修正する必要があると __思うかもしれません__。

下記のプログラムは、6 行目に __`return board` を追加__ し、12、14 行目で __グローバル変数 `board`__ に `place_mark` の __返り値を代入__ するように修正しています。

```python
 1  def place_mark(board, x, y, mark):
 2      if board[x][y] == " ":
 3          board[x][y] = mark
 4      else:
 5          print("(", x, ",", y, ") のマスにはマークが配置済です")
 6      return board
 7
 8   def initialize_board():
 9      return [[" "] * 3 for x in range(3)]
10
11  board = initialize_board()             # 初期化されたゲーム盤のデータを返す関数を呼び出す
12  board = place_mark(board, 0, 1, "〇")  # (0, 1) のマスに 〇 を配置する
13  print(board)
14  board = place_mark(board, 0, 1, "×")   # (0, 1) のマスに × を配置する
15  print(board)
```

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

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

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

board = initialize_board()             # 初期化されたゲーム盤のデータを返す関数を呼び出す
board = place_mark(board, 0, 1, "〇")  # (0, 1) のマスに 〇 を配置する
print(board)
board = place_mark(board, 0, 1, "×")   # (0, 1) のマスに × を配置する
print(board)
```
</details>

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

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

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

board = initialize_board()               # 初期化されたゲーム盤のデータを返す関数を呼び出す
- place_mark(board, 0, 1, "〇")          # (0, 1) のマスに 〇 を配置する
+ board = place_mark(board, 0, 1, "〇")  # (0, 1) のマスに 〇 を配置する
print(board)
- place_mark(board, 0, 1, "×")           # (0, 1) のマスに × を配置する
+ board = place_mark(board, 0, 1, "×")   # (0, 1) のマスに × を配置する
print(board)
```
</details>

実行結果
```
[[' ', '〇', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
( 0 , 1 ) のマスにはマークが配置済です
[[' ', '〇', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
```

実行結果から確認できるように、このような修正を行ってもプログラムは __正しく動作します__ が、実際にはこのような __修正を行う必要はありません__。その理由は、`place_mark` のブロックの中の処理では、__`board` に__ 値を __代入__ するの __ではなく__、__`board` の要素__ に 値を __代入している__ からです。

`place_mark(board, 0, 1, "〇")` の関数呼び出しによって、グローバル変数 `board` とローカル変数 `board` は __同じデータを管理__ する __オブジェクトを共有する__ ようになります。3 行目の `board[x][y] = mark` によって、__2 次元配列を表す list__ が代入されている `board` の (x, y) のマスを表す __要素に__ `mark` が代入されますが、その際に、グローバル変数 `board` とローカル変数 `board` の __共有は解除されません__。従って、ローカル変数 `board` の要素を変更すると、__同時にグローバル変数 `board` の要素も変更されます__。そのため、`place_mark` の場合は、ローカル変数 `board` を __返り値として返す必要はありません__。

なお、`initialize_board` の場合は、`place_mark` のように __既に存在する__ ゲーム盤のデータの __一部を変更__ するの __ではなく__ 、新しいゲーム盤のデータを __無から作成している__ ので、返り値としてゲーム盤のデータを返す必要があります。

関数の __ブロック内の処理__ によって、__グローバル変数の値が変更される__ ことがピンとこない人がいるかもしれませんので、次はその点について説明します。

# 関数の副作用

関数呼び出しの処理によって、関数の __ブロックの中__ で、関数の外の __グローバル変数の値が変化__ することを、関数の __副作用__ と呼びます。

前回の記事で、「関数の __ブロックの中の変数__ に、__仮引数__ か、__ブロックの中で値を代入した変数のみ__ を記述する」という条件を守った場合、関数の内外で __ローカル変数とグローバル変数__ を __分離する__ ことができるという説明をしました。ただし、今回の記事で `global` を使って関数のブロックの中でグローバル変数に値を代入する方法を説明しましたので、上記の条件は __`global` を使わない場合__ の条件です。

先程修正した `initialize_board` は上記の条件を __満たしており__、`initialize_board` の関数呼び出しによって、どのグローバル変数の値も __変化しない__ ので、関数の __副作用は発生しません__。

一方、先程修正した `place_mark` は上記の条件を __満たしていますが__、`place_mark` の関数呼び出しによって、__グローバル変数 `board`__ の値が __変化する__ ので、関数の __副作用が発生しています__。

このことから、__上記の条件を満たしても__、関数の副作用が __発生する場合__ と __発生しない場合__ がある事がわかります。

そこで、「関数の __ブロックの中の変数__ は、__仮引数__ か、__ブロックの中で値を代入した変数のみ__ を記述する」という __条件が満たされた場合__ に、関数の __副作用が発生する条件__ について説明します。

## global を使う場合

`global` を使うと、関数のブロックの中でグローバル変数に値を直接代入できるので、__`global`__ が記述された関数内で、グローバル変数に値を代入した場合は、__関数の副作用が発生します__。

以後の説明は、関数のブロックの中で、__`global` を記述しない__ 場合の説明です。

## 関数に仮引数が存在しない場合

関数に仮引数が存在しない場合は、関数のブロックの外の __グローバル変数の値__ は、関数の __ブロック内で行われる処理__ に一切 __影響を及ぼしません__。また、関数のブロック内ではグローバル変数に値を代入することはできないので、__関数の副作用は発生しません__。

## 関数に仮引数が存在する場合

関数に仮引数が存在する場合は、関数の __仮引数に__、関数呼び出しの __実引数の値が代入__ され、関数のブロック内で __仮引数の値を使って__ 処理が行われます。

### 仮引数の値の一部を変更しない場合

副作用は、複数の変数が共有するデータの __一部を変更する際に発生する__ ものなので、仮引数が存在する場合でも、__仮引数の値を利用するだけ__ で、仮引数の __値の一部を更しない__ 場合は _副作用は発生しません__。

ここでいう、__仮引数の値の一部の変更__ とは、仮引数に直接値を代入するの __ではなく__、list の要素に値を代入するような、仮引数が __参照するオブジェクトを変更せず__ に、仮引数に代入された __値の一部を変更__ するような処理の事を指します。

### 実引数が変数のみの式の場合

実引数が `a` のような __変数のみの式__ の場合、過去の記事で説明したように、仮引数に実引数を代入する処理によって、仮引数と実引数が __同じオブジェクトを共有__ するようになります。

#### 仮引数に代入されたデータが、疑似的な複製が行われるデータの場合

「__疑似的な複製__ が行われるデータ」は、本記事が「その 11」の記事で __独自に定義した用語__ で、「データを構成する、__全てのオブジェクト__ が管理する   __データ__ が __イミュータブル__[^2] なデータ」のことです。良く使われる疑似的な複製が行われるデータ型には、__数値型__、__文字列型__、__論理型__、__None 型__ のデータが挙げられます。

疑似的な複製が行われるデータが代入された変数は、その性質から、__別のデータ__ で変数の値を __上書き__ する __代入以外__ の処理で __値を変更することはできません__。また、仮引数に値を代入すると、仮引数と実引数の __オブジェクトの共有が解除__ されます。従って、仮引数に対してどのような処理を行っても、__実引数に記述したグローバル変数__ の値が __変化することはありません__。従って、この場合は __関数の副作用は発生しません__。

分かりにくいと思いますので、具体例を挙げます。下記のプログラムは過去の記事で紹介した `assign_one` と同じプログラムです。5 行目で、`assign_one` の関数呼び出しの __実引数__ に __グローバル変数 `a` を記述__ しているため、__グローバル変数 `a`__ と `assign_one` の __ローカル変数__ である仮引数 __`a`__ が同じオブジェクトを __共有__ します。

ローカル変数 `a` に代入された `0` は、データを構成する、__全て__ のオブジェクトが管理する __データ__ が __イミュータブルなデータ__ なので、新しいデータを __代入する以外の方法__ で、変数の __値を変更することはできません__。また、2 行目のような __代入処理を行う__ と、グローバル変数 `a` とローカル変数 `a` の __共有が解除__ されてしまいます。従って、ローカル変数 `a` に対してどのような処理を行っても、__グローバル変数 `a` の値__ を __変化させることはできません__。この説明は、過去の記事の __プログラム A__ の説明と同じなので忘れた方は「その 11」前後の記事を復習して下さい。

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

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

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

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

実行結果
```
0
```

#### 仮引数に代入されたデータが、疑似的な複製が行われないデータの場合

「疑似的な複製が __行われない__ データ」とは、「データを構成する、__1 つ以上のオブジェクト__ が管理するデータが __ミュータブル__[^3] なデータ」のことです。代表的なデータ型には、__list__ や、本記事ではまだ説明していない __dict__ などがあります。

疑似的な複製が行われないデータが代入された変数は、その性質から、データを構成する __ミュータブルなデータ__ の値を __上書き__ して __変更する__ ことが出来ます。従って、__実引数に記述したグローバル変数__ の値が __変化することがある__ ため、この場合は、__関数の副作用は発生する可能性__ があります。

:::note
例えば、__仮引数__ に対して __何の処理も行わない__ ような関数の場合は、副作用は発生しないので、上記では「__発生する可能性がある__」という表現になっています。
:::

分かりにくいと思いますので、具体例を挙げます。下記のプログラムは先ほど修正した `place_mark` を再掲したものです。11 行目で、`place_mark` の関数呼び出しの __最初の実引数__ に、グローバル変数 `board` を記述しているので、グローバル変数 `board` と `place_mark` のローカル変数 `board` が __同じオブジェクトを共有__ します。

ローカル変数 `board` に代入された list はミュータブルなデータなので、3 行目のように、その要素に値を代入することが出来ます。その際にグローバル変数 `a` とローカル変数 `a` の __共有は解除されません__。従って、3 行目の処理を行うと、__グローバル変数 `board` の値__ が __変化__ してしまいます。この説明は、過去の記事の __プログラム B__ の説明と同じなので忘れた方は「その 11」前後の記事を復習して下さい。

```python
 1  def place_mark(board, x, y, mark):
 2      if board[x][y] == " ":
 3          board[x][y] = mark
 4      else:
 5          print("(", x, ",", y, ") のマスにはマークが配置済です")
 6
 7  def initialize_board():
 8      return [[" "] * 3 for x in range(3)]
 9
10  board = initialize_board()   # 初期化されたゲーム盤のデータを返す関数を呼び出す
11  place_mark(board, 0, 1, "〇")  # (0, 1) のマスに 〇 を配置する
12  print(board)
13  place_mark(board, 0, 1, "×")   # (0, 1) のマスに × を配置する
14  print(board)
```

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

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

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

board = initialize_board()   # 初期化されたゲーム盤のデータを返す関数を呼び出す
place_mark(board, 0, 1, "〇")  # (0, 1) のマスに 〇 を配置する
print(board)
place_mark(board, 0, 1, "×")   # (0, 1) のマスに × を配置する
print(board)
```
</details>

#### 厳密な副作用が起きる範囲

厳密には、__仮引数に代入されたデータ__ を __共有__、または __部分的に共有__ する __グローバル変数__ に対して __のみ__、副作用が発生します。__部分的な共有__ とは、__異なる変数__ に代入された __異なるデータ__ が、__一部のデータ__ を __共有する状態__ の事を指します。詳細は「その 11」の記事で説明しているので忘れた方は復習して下さい。

おそらくこの説明で意味が分かる人は少ないと思いますので具体例を挙げます。下記のプログラムは、初期化されたゲーム盤の __各行__ を __`row0`__、__`row1`__、__`row2`__ という __グローバル変数に代入__ し、それらを使って __グローバル変数 `board`__ に初期化されたゲーム盤を代入しています。

この場合は、__`board`__ に代入されたデータを __構成する 3 つのオブジェクト__ をグローバル変数 __`row0`__、__`row1`__、__`row2`__ が参照しているので、__`board`__ と __`row0`__ などは、データを __部分的に共有__ しています。

このような場合に `place_mark` で (0, 1) のマスに 〇 を配置すると、下記の実行結果のように、__`board` だけでなく__、`board` に代入されたデータを構成するオブジェクトの 1 つを参照する __`row0`__ の値も __変更__ されてしまいます。

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

row0 = [" ", " ", " "]
row1 = [" ", " ", " "]
row2 = [" ", " ", " "]
board = [row0, row1, row2]
place_mark(board, 0, 1, "〇")
print(board)
print(row0)
```
実行結果
```
[[' ', '〇', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
[' ', '〇', ' ']
```

### 実引数が変数のみ式でない場合

実引数が `a + b` のような、__変数のみ式でない__ 場合も、副作用が発生する条件は、実引数が変数のみ式の場合と __大きく変わることはありません__。

実引数の式の __計算結果__ が __疑似的な複製が行われるデータ__ であれば、__副作用は発生しません__し、そうでなければ先ほど __厳密な副作用が起きる範囲__ で説明した場合と __同じ範囲__ で __副作用が発生する可能性__ があります。具体例を挙げて説明します。

先程のプログラムでは、初期化されたゲーム盤のデータを __グローバル変数 `board` に代入__ し、その __`board` を `place_mark` の実引数に記述__ していました。

一方、下記のプログラムのように、初期化されたゲーム盤のデータを __グローバル変数に代入せず__ に、`[row0, row1, row2]` のように、__直接__ `place_mark` の __実引数に記述__ することもできます。下記のプログラムでは、実引数が __変数のみの式ではありません__ が、実行結果からわかるように、グローバル変数 `row0` の値が変化しているので、__副作用が発生する__ ことがわかります。

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

row0 = [" ", " ", " "]
row1 = [" ", " ", " "]
row2 = [" ", " ", " "]
place_mark([row0, row1, row2], 0, 1, "〇")
print(row0)
```

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

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

row0 = [" ", " ", " "]
row1 = [" ", " ", " "]
row2 = [" ", " ", " "]
- board = [row0, row1, row2]
- place_mark(board, 0, 1, "〇")
+ place_mark([row0, row1, row2], 0, 1, "〇")
- print(board)
print(row0)
```
</details>


実行結果
```
[' ', '〇', ' ']
```

## 副作用が起きる範囲の制御

ここまでの説明からわかるように、「関数のブロックの中の変数に、仮引数か、ブロックの中で値を代入した変数のみを記述する」と「global を使用しない」という条件を満たした場合に、関数の副作用は __無秩序に発生する__ わけでは __ありません__。具体的には、__仮引数__ に代入されたデータと __無関係なグローバル変数__ に対しては __副作用は発生しません__。

このことから、上記の条件を満たした場合は、__実引数によって__、関数の副作用が発生する可能性のあるグローバル変数を __制御することができる__ ことがわかります。


## 関数の副作用のまとめ

:::note warn
下記の __2 つの条件__ を満たした場合の関数の副作用をまとめます。

* 関数の __ブロックの中の変数__ に、__仮引数__ か、__ブロックの中で値を代入した変数のみ__ を記述する
* global を __使用しない__

下記の __いずれかの条件__ を満たす場合は、関数の __副作用は発生しません__。

* __仮引数が存在しない__ 場合
* 関数のブロックの中で、仮引数に代入された __値の一部を変更しない__ 場合
* 仮引数に代入された値が、__疑似的な複製が行われる__ データである場合

上記以外の場合は、仮引数に代入されたデータと __関係のある__ グローバル変数に対して __のみ__ 副作用が発生する可能性がある。ここでいう __関係のある__ とは、__仮引数に代入されたデータ__ を __共有__、または __部分的に共有__ する __グローバル変数__ のことである。
:::



# 名前空間とスコープが必要になる理由

ここまでの説明で、名前空間とスコープの扱い方がなんとなくわかってきた人が多いと思いますが、この仕組みは複雑なだけで、__メリットを感じられない__ 人が多いのではないかと思います。そこで、今回の記事では最後に名前空間とスコープが __必要になる理由__ について説明します。

## 異なる関数で同じ名前のローカル変数を使う

プログラムでは、__異なる関数__ で __同じような意味__ を持つデータを代入する __ローカル変数を扱う__ 場合が良くあります。例えば、`place_mark` という関数は、__ゲーム盤__ と __x 座標__ と __y 座標__ のデータを扱うので、__ローカル変数__ である __仮引数の名前__ に下記のように __`board`__ と __`x`__ と __`y`__ という名前を付けました。

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

次に、ゲーム盤の (x, y) のマスのマークを取り除く（remove）、`remove_mark` という関数を定義する事を考えてみて下さい。`remove_mark` も __ゲーム盤__ と __x 座標__ と __y 座標__ のデータを扱うので、下記のプログラムのように、__仮引数の名前__ に __`board`__ と __`x`__ と __`y`__ という名前を付けるのが自然でしょう。

マークを取り除くということは、(x, y) のマスを表す要素に __半角の空白文字を代入__ すればよいので、`remove_mark` は下記のプログラムのように定義する事ができます。

```python
def remove_mark(board, x, y):
    board[x][y] = " "
```

:::note
(x, y) のマスが空白の場合にこの処理を実行すると、空白のマスを空白にするという処理が行われるので、__問題は発生しません__。そのため `place_mark` と異なり、`remove_mark` では if 文による空白のマスであるかどうかのチェックを __行う必要はありません__。
:::

`place_mark` と `remove_mark` はどちらも __ゲーム盤__ と __x 座標__ と __y 座標__ を表すデータを代入する __`board`__ と __`x`__ と __`y`__ という __同じ名前の仮引数__ を持ちますが、それらは __名前は同じ__ ですが __別の変数__ です。

__異なる変数に__ 対して __必ず異なる名前__ を付けなければ __ならない__ 場合は、`place_mark` と `remove_mark` を __同じプログラムで同時に定義__ する際に、下記のプログラムのようにそれぞれの関数の仮引数の __名前をすべて異なる名前__ にする __必要__ があります。

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

def remove_mark(board2, x2, y2):
    board2[x2][y2] = " "
```

__ゲーム盤__ と __x 座標と y 座標__ を表すデータを __入力__ とする関数を __さらに定義__ する場合は、その関数の仮引数の名前を、上記の `board`、`x`、`y`、`board2`、`y2`、`y2` __以外の名前にする必要__ があります。さすがに、それは大変すぎますし、そのような関数が増えれば増えるほど、既に定義した関数の仮引数に使った名前と __重複しない__ 変数名を考えることは __困難になります__。

__名前空間の仕組み__ があれば、関数呼び出しのたびに __新しいローカル名前空間が作成__ されます。__同じ名前__ のローカル変数であっても、その変数名を管理する __名前空間が異なれば__、__異なるオブジェクトに対応づけられる__ ので、__異なる関数__ の中で __同じ名前のローカル変数__ を使っても、それらを __異なる変数__ として __区別できる__ ようになります。

そのため、__ゲーム盤__ と __x 座標 と y 座標__ を表すデータを仮引数とする関数は __`board`__ と __`x`__ と __`y`__ という __共通の名前__ の仮引数を使って関数の定義を行うことが出来るようになります。従って、下記のよう、同じプログラムの中に、__同じ名前__ の仮引数を持つ、`place_mark` と `remove_mark` を __同時に定義__ して利用することができます。

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

def remove_mark(board, x, y):
    board[x][y] = " "
```

なお、上記の例では異なる関数で、__同じ名前の仮引数__ を記述する例を挙げましたが、異なる関数で __仮引数以外__ の __同じ名前のローカル変数__ を __記述__ することもできます。

:::note warn
名前空間の仕組みがあることで、__他の関数で使われたローカル変数の名前__ を __気にすることなく__、__関数の定義を記述__ することが出来るようになる。
:::

## グローバル変数とローカル変数で同じ名前を使う

上記の場合は、__異なる関数__ で __同じ名前__ の __ローカル変数__ を使うという例でしたが、__グローバル変数とローカル変数__ に __同じ名前を使える__ と便利な場合があります。

例えば、`place_mark` を使って、全てのマスに 〇 を配置するプログラムを作成することを考えてみて下さい。そのような処理は、「その 6」の記事で紹介した、__for 文を入れ子にした繰り返し処理__ を使って、下記のようなプログラムで記述することが出来ます。

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

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

# 初期化されたゲーム盤のデータを返す関数を呼び出す
board = initialize_board()  
# 各列の処理を繰り返し処理で行う
for x in range(3):
    # x 列の、各行の処理を繰り返し処理で行う
    for y in range(3):
        # (x, y) のマスに "〇" を配置する
        place_mark(board, x, y, "〇")
print(board)
```
実行結果
```
[['〇', '〇', '〇'], ['〇', '〇', '〇'], ['〇', '〇', '〇']]
```

上記のプログラムでは、2 つの __入れ子になった__ for 文で、列を表す x 座標と、行を表す y 座標の値を、for 文による繰り返し処理のたびに `range(3)` から取り出して利用していますが、その際に取り出した値を __`x`__ と __`y`__ という名前の __グローバル変数に代入__ しています。また、ゲーム盤のデータは、__`board`__ という名前の __グローバル変数に代入__ しています。

__異なる変数に__ 対して __必ず異なる名前__ を付けなければ __ならない場合__ は、この 3 つのグローバル変数 `x`、`y`、`board` と、`place_mark` の仮引数である __ローカル変数__ `x`、`y`、`board` の名前をそれぞれ __異なる名前にする必要__ がありますが、それは __大変__ だと思いませんか？

名前空間の仕組みがあれば、グローバル変数と、ローカル変数は、__同じ名前であっても異なる変数__ として扱われます。従って、上記のプログラムのように、__同じ名前__ のグローバル変数とローカル変数を記述しても、それらは __別の変数として区別される__ ので、プログラムは __正しく動作__ します。

:::note warn
名前空間の仕組みがあることで、以下のようなメリットが得られる。

* __他の関数__ のブロックの中で使われた __ローカル変数の名前__ を __気にすることなく__、__関数のブロックの中__ で __ローカル変数の名前を付ける__ ことが出来るようになる。

* 関数の外で使われた __グローバル変数の名前__ を __気にすることなく__、__関数のブロックの中__ で __ローカル変数の名前を付ける__ ことが出来るようになる。

* __すべての関数__ のブロックの中で使われた __ローカル変数の名前__ を __気にすることなく__、__関数のブロックの外__ で __グローバル変数の名前を付ける__ ことが出来るようになる。

:::

## 異なるモジュールで同じ名前を使えるようにする

名前空間が必要となる他の状況として、__ファイルに保存__ された __Python のプログラム__ である __モジュール__ を、自分のプログラムに __インポートして利用__ したい場合があります。モジュールには、__様々な変数や関数が記述__ されていますが、それらの変数名や関数名と __同じ名前__ の変数や関数を自分のプログラムで __使えないと__、プログラムを記述することが __非常に困難__ になってしまいます。

名前空間の仕組みがあれば、他のモジュールをインポートして利用する際に、インポートするモジュール内で __どのような変数名や関数名が記述されているか__ について、__気にすることなく__ 自分のプログラムを記述することが出来るようになります。

モジュールのインポートの方法と、インポートしたモジュール内の名前の記述方法については、必要になった時点で説明します。

:::note warn
名前空間の仕組みがあることで、__他のモジュールで使われた名前__ を __気にすることなく__、__プログラムを記述__ することが出来るようになる。
:::

## まとめ

:::note warn
名前空間の仕組みによって、__異なる名前空間__ で使われている __名前を気にすることなく__、__名前を記述__ できるようになるというメリットが得られる。
:::

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

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

おそらく、VSCode のバージョンアップが行われたため、下記のリンク先の github 上で marubatsu.ipynb を見た場合に、エラーの表示に、`<a href='vscode-notebook-cell:/c%3A/Users/ys/ai/marubatsu/016/marubatsu.ipynb#W0sZmlsZQ%3D%3D?line=7'>8\</a>` のような HTML のリンクが表示されるようになりました。marubatsu.ipynb をダウンロードして、自分の PC で VSCode 上で実行することで、エラーの表示が正しく表示されるようになります。

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

# 次回の記事

https://qiita.com/ysgeso/items/4d55e73d6571fe85aa49

# 更新履歴

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


[^1]:`global` 以外にも、`nonlocal 変数名` と記述することで、その名前をローカル変数でない変数とすることができます。`nonlocal` については、必要になった時点で説明します
[^2]:イミュータブルなデータとは、オブジェクトが管理するデータを __後から書き換えることができない__ データの事です。詳細は「その 11」の記事を参照して下さい
[^3]:ミュータブルなデータとは、オブジェクトが管理するデータを __後から書き換えることができる__ データの事です。詳細は「その 11」の記事を参照して下さい