Pythonで〇×ゲームのAIを一から作成する　その17　モジュールのインポートと docstring

# 目次と前回の記事

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

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

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

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

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

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

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

## 前回までのおさらい

前回までの記事で、下記の処理を行う関数を定義しました。

* 初期化されたゲーム盤を返す `initialize_board`
* 指定したマスにマークを配置する `place_mark`

# モジュールの作成とインポート

本記事では、毎回 __新しい__ marubatsu.ipynb という名前のファイルを作成し、そこに各回の記事で紹介したプログラムを記述してプログラムを実行しています。

これまでは、`initialize_board` などの関数を __修正しながら説明__ していたので、JupyterLab のセルに __毎回__ `initialize_board` などの __関数の定義を記述__ していましたが、__完成した__  `initialize_board` などの関数の定義を __毎回記述__ するのは __大変__ です。

そのような場合は、__別のファイル__ に関数の定義などを記述し、marubatsu.ipynb の中で、そのファイルに定義された __関数を利用__ するという方法があります。

__ファイル__ に記述された python のプログラムのことを __モジュール__ と呼び、自分のプログラムで __モジュール__ に記述されたプログラムを __利用する__ ことをモジュールを __インポート__ すると呼びます。

## モジュールの作成

モジュールの作成は Python のプログラムを __ファイルに記述して保存__ するだけで簡単に行うことが出来ます。その際に、__拡張子が .py__ のファイルに Python のプログラムを保存します。

__ファイルの名前がモジュールの名前になる__ ので、保存の際にはモジュールに記述したプログラムの __内容に合わせた名前__ を付けるのが一般的です。

:::note
拡張子が .ipynb の JupyteLab のファイルをモジュールとして __利用することはできません__。その理由は、JupyterLab のファイルには、プログラムの実行結果などの、__Python のプログラム以外のデータが混じっている__ からです。
:::

VSCode では、以下の手順でモジュールを作成することが出来ます。

1. 「ファイル」メニュー→「新しいファイル」→「Python ファイル」を選択する
2. 新しいファイルに Python のプログラムを記述する
3. 「ファイル」メニュー→「保存」を選択（または Ctrl + S）して、ファイルの保存パネルを開く
4. ファイルの名前を入力する
5. ファイルを保存するフォルダを選択し、保存ボタンをクリックする

実際に、上記の手順で新しいファイルを作成し、前回の記事で定義した `initialize_board` と `place_mark` が定義された下記のプログラムを入力して、__marubatsu.py という名前__ で marubatsu.ipynb と __同じフォルダ__ に保存して下さい。

```python marubatsu.py
def initialize_board():
    return [[" "] * 3 for x in range(3)]

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

## モジュールのインポート

__同じフォルダに保存__ されたモジュールは、下記のように記述することでインポートすることが出来ます。

```python
import モジュール名
```

インポートしたモジュールの中に記述された __変数や関数__ などの __名前__ は、下記のように記述することで利用することが出来ます。モジュール名の直後には __半角__ の __.（ピリオド）__ を記述します。

```python
モジュール名.名前
```

下記のプログラムは、1 行目で先ほどファイルに保存した __marubatsu__ というモジュールを __インポート__ し、3 行目で、そのモジュールの __中で定義__ された __`intialize_board` を呼び出して__ います。実行結果から、正しくプログラムが動作することを確認することが出来ます。

```python
import marubatsu

board = marubatsu.initialize_board()
print(board)
```
実行結果
```
[[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
```

:::note warn
モジュールをインポートすることによって、インポートした __モジュールのグローバル名前空間__ に登録された __名前を利用__ することが出来るようになります。また、その際に __名前の前__ に「__モジュール名.__」を記述するので、__異なるモジュール__ のグローバル名前空間で __同じ名前__ が使われていたとしても、それらの __名前を区別して利用__ することが出来ます。
:::

## 異なるフォルダに保存されたモジュールのインポート

__異なるフォルダ__ に保存されたモジュールのインポートには、2 通りの方法があります。

モジュールのファイルが、__フォルダの中に保存__ されている場合は、以下のように記述することでモジュールをインポートすることが出来ます。

```python
import フォルダ名.モジュールの名前
```

例えば、marubatsu.py を、__marubatsu.ipynb が保存__ されている __フォルダ内__ の __lib__ という __フォルダの中に保存__ した場合は、下記のプログラムのように記述することでその marubatsu モジュールをインポートすることが出来ます。

:::note
モジュールのことを __ライブラリ（library）__ と呼ぶことがあります。そのため、__モジュール__ のファイルを __保存するフォルダの名前__ に library を省略した __lib という名前__ を付けることがあります。
:::

```python
import lib.marubatsu

board = lib.marubatsu.initialize_board()
print(board)
```
実行結果
```
[[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
```

モジュールが保存されているフォルダが、__より深い場所__ に存在する場合は、同様の方法で、__フォルダ名を半角の `.`（ピリオド）でつなげて__ 表記します。

もう一つの方法は、__`sys.path`__ を使ってモジュールのファイルが保存されたフォルダのパス（フォルダの住所の事です）を指定するという方法です。本記事ではこの方法は使いませんので、ここではその方法については説明しません。興味がある方は調べてみて下さい。

## 標準ライブラリのインポート

Python には __あらかじめ__ いくつかのモジュールが作成されており、それらのモジュールの事を __標準ライブラリ__ と呼びます。標準ライブラリは、自分のプログラムを記述する Python のファイルがどこに保存されている場合でも、__単に `import 標準ライブラリの名前`__ と記述してインポートすることが出来ます。

標準ライブラリの詳細については下記のリンク先を参照して下さい。

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

## 自分でインストールしたモジュールのインポート

__他人が作成したモジュール__ の具体的なインストールの方法については今後の記事で説明しますが、Anaconda Navigator や pip 等のコマンドによって __インストールしたモジュール__ をインポートする場合も、標準ライブラリと同様に、__単に `import モジュールの名前`__ と記述してインポートすることが出来ます。

## モジュールのインポートの仕組み

モジュールをインポートすると、そのモジュールの __グローバル名前空間が作成__ されます。Python では __名前空間もデータの一種__ なので、他のデータと同様に __名前空間のデータ__ は __オブジェクトによって管理__ されます。

`import モジュール名` によってインポートされた __モジュール名__ は、__変数名や関数名__ と __全く同じ仕組み__ で __メインモジュールのグローバル名前空間が管理__ します。

具体的には、モジュールをインポートすると以下のような手順で処理が行われます。

1. __インポートしたモジュールのグローバル名前空間__ のデータを __管理する新しいオブジェクトが作成__ される

2. __モジュールの中__ に記述された __Python のプログラムがすべて実行__ され、__そのモジュールのグローバル名前空間__ に、モジュールに記述された __グローバル変数やグローバル関数__ などの __名前が登録__ される

3. __メインモジュールのグローバル名前空間__ に __インポートしたモジュールの名前__ が __登録__ され、手順 1 で作成したオブジェクトに __対応づけられる__

下図は、`import marubatsu` を実行した場合のオブジェクトの様子を図示したものです。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/6bcc8234-69d7-e784-cb55-9ffc436a595c.png" width="700">

__モジュール名は変数名__ と __同じ性質__ を持つので、変数名や関数名の場合と同様に、モジュール名に対して __値を代入__ することが出来ます。

下記のプログラムでは、3 行目でインポートした __モジュール名__ である __`marubatsu`__ を __`print` で表示__ しています。モジュールの名前を `print` で表示すると、下記の実行結果のように、「\<module \`モジュール名\` from \`モジュールが保存されたファイルのパス\`\>」が表示されます。

4 行目で、`marubatsu` に `1` を代入すると、`marubatsu` の値が `1` で __上書きされる__ ので、5 行目のように __`print(marubatsu)`__ を実行すると __`1` が表示__ され、__4 行目以降__ で、`marubatsu` を __モジュールの名前として__ 利用することは __できなくなります__。

```python
import marubatsu

print(marubatsu)
marubatsu = 1
print(marubatsu)
```
実行結果
```
<module 'marubatsu' from 'c:\\Users\\ys\\ai\\marubatsu\\017\\marubatsu.py'>
1
```

:::note
上記のプログラムは、モジュール名に値を代入できることを __示すため__ に記述したプログラムです。実際には上記のように __モジュール名に値を代入する__ ようなプログラムを __記述する__ ことは、__ほとんどありません__。
:::

このように、モジュール名に関する処理は、__変数と同じ仕組み__ によって行われます。これまでに説明していないような、全く新しいことが行われているわけではありません。

:::note warn
以下は __`import モジュール名`__ に関するまとめです。

* __同じフォルダに保存__ された __モジュール名.py__ というファイルに保存されたモジュールを __インポート__ して自分のプログラムで __利用__ できるようになる

* インポートしたモジュールの __プログラムが実行__ され、その __モジュールのグローバル名前空間__ に、そのモジュールの __グローバル変数名やグローバル関数名__ が __登録__ される

* __名前空間のデータ__ は、他のデータと同様に __オブジェクトによって管理__ される

* __メインモジュールのグローバル名前空間__ に、__モジュール名__ が __登録__ され、__インポートしたモジュールのグローバル名前空間__ を管理するオブジェクトに __対応づけられる__

* インポートしたモジュールの __グローバル名前空間に登録された名前__ は、__`モジュール名.名前`__ と記述することで自分のプログラムで __利用__ することが出来る
:::

## 複数回のモジュールのインポート

下記のプログラムのように、__同じモジュールを複数回インポート__ するようなプログラムを記述した場合に行われる処理について説明します。

```python
import marubatsu
import marubatsu # 既にインポートされたモジュールをもう一度インポートする
```

このようなプログラムを記述した場合は、__2 回目以降__ のモジュールのインポートでは __何の処理も行われません__。従って、__2 回目以降__ のモジュールのインポートによって、そのモジュールの __プログラムが実行__ されることは __ありません__。

具体例を挙げます。下記の、`1` という表示を行うプログラムを print_1.py という名前のファイルに保存することで、__print_1 という名前のモジュールを作成__ します。モジュールは Python のプログラムであれば __その内容は何でもかまわない__ ので、下記のように、__変数や関数が存在しない__ ファイルも __モジュールです__。

```python print_1.py
print(1)
```

次に、下記のプログラムのように、この __print_1__ というモジュールを __複数回インポート__ するプログラムを記述して実行します。このプログラムでは、モジュールを __インポートした際__ にそのモジュールのプログラムが __実行されているかどうかを確認__ できるように、モジュールを __インポートする前__ の 1、3 行目で __メッセージを表示__ しています。

```python
print("これから print_1 をインポートします")
import print_1
print("もう一度 print_1 をインポートします")
import print_1
```
実行結果
```
これから print_1 をインポートします
1
もう一度 print_1 をインポートします
```

実行結果からわかるように、__一度目の `import print_y`__ を実行すると、__print_y.py__ のプログラムが __実行されて `1` が表示されます__ が、__二度目__ の `import print_y` を実行しても print_y.py のプログラムは __実行されない__ のでもう一度 `1` が __表示されることはありません__。

:::note warn
同じモジュールを __複数回インポート__ しても、インポートされたモジュールの __プログラムが実行される__ のは、__最初に__ モジュールを __インポートした時だけ__ である。
:::

## インポート後のモジュールの修正

上記の性質から、__JupyterLab で__ モジュールを __インポートした後__ で、モジュールの __ファイルの内容を修正__ して保存した場合に、__もう一度__ そのモジュールを __インポートしても__ 、__修正後のモジュールはインポートされません__。

JupyterLab で __修正後のモジュールをインポート__ する方法には、以下の 2 通りの方法があります。

### JupyterLab を再起動する

JupyterLab の上部にある「__再起動__」ボタンをクリックすることで、__プログラムが強制的に終了__ されるので、その後でモジュールをインポートするプログラムを実行すると、__修正後のモジュールがインポート__ されます。

### importlib.reload を使う

標準ライブラリには、__importlib__ という __モジュールのインポートに関する関数が定義__ されたモジュールがあります。importlib 内で定義された __`reload`__ という関数の __実引数にモジュールの名前__ を記述して呼び出すことで、そのモジュールを __再読み込み（reload）__ することが出来ます。

:::note
liportlib の詳細については、下記のリンク先を参照して下さい。
:::

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

importlib を利用するためには、下記のプログラムの 1 行目のように `import importlib` を記述します。下記のプログラムは、5 行目で __`importlib.reload`__ を使って `print_1` のモジュールを __再読み込み__ しているので、5 行目を実行した際に __`1` が表示__ されます。

なお、下記のプログラムは __JupyterLab を再起動__ してから実行して下さい。再起動せずに実行すると、下記のプログラムを実行する前に先ほど `print_1` をインポートしてしまったので、3 行目で `import print_1` を実行しても `1` は表示されません。

```python
import importlib
print("これから print_1 をインポートします")
import print_1
print("print_1 を再読み込みします")
importlib.reload(print_1)
```
実行結果
```
これから print_1 をインポートします
1
print_1 を再読み込みします
1
<module 'print_1' from 'c:\\Users\\ys\\ai\\marubatsu\\017\\print_1.py'>
```

:::note
これまでのプログラムでは、__`print`__ を使って __表示__ を行っていましたが、__JupyterLab__ で __セルの最後の文__ が __データを表す__ 場合、そのセルを実行すると __最後の文のデータが表示__ されます。上記の実行結果の最後に「\<module \'print_1\' from \'c:\\\\Users\\\\ys\\\\ai\\\\marubatsu\\\\017\\\\print_1.py\'\>」が表示されているのは、`importlib.reload` の __返り値__ が、__再読み込みしたモジュールを表すデータ__ であるからです。

例えば、下記のプログラムは、__最後の行__ に __`1` が代入された `a` が記述__ されているので、`print` を使わなくても __`1` が表示__ されます。

```python
a = 1
a
```
実行結果
```
1
```

なお、__データの種類__ によっては、 __`print` で表示__ する場合と、__最後の行にデータを記述して表示__ する場合で __異なる表示__ が行われる場合があります。下記のプログラムは __関数名__ を `print` と、セルの最後の行に記述して表示するプログラムで、実行結果のように、異なる表示が行われます。

__本記事では__ 特別な理由がない限り、__`print` を使って表示を行う__ ことにします。

```python
def print_1():
    print(1)
    
print(print_1)
print_1
```
実行結果
```
<function print_1 at 0x0000024E15002C00>
<function __main__.print_1()>
```

ちなみに、`print` で関数名を表示した場合に最後に表示されるのは、関数の定義を管理する __オブジェクトの id__ です。関数名を最後の行に記述した場合に function の後に表示されるのは、「その関数が定義されたモジュールの名前.関数の名前(仮引数)」です。メインモジュールの名前は自動的に __\_\_main\_\___ という名前が付けられるので上記のように表示されます。
:::

## モジュールのインポートに関する補足

上記がモジュールのインポートに関する __基本的な説明__ ですが、モジュールのインポートには __他の良く使われる記述方法__ があるので、それらについて説明します。

### 複数のモジュールをまとめてインポートする方法

`import` の後に、__半角の `,` で区切って複数のモジュール名__ を記述することで、__複数のモジュールをまとめてインポート__ することが出来ます。下記のプログラムはその具体例です。

```python
import marubatsu, print_1
```

### 別名によるモジュールのインポート

以下のように記述することで、モジュールを __別の名前でインポート__ することが出来ます。

```python
import モジュール名 as モジュールの別名
```

別名によるモジュールのインポートは主に以下のような場合に使用します。

* モジュールの名前が `marubatsu` のように __長い場合__ で、`mb` のように __短い名前__ でモジュールをインポートすることで、モジュールに関するプログラムを __短く記述__ したい場合
* __自分のプログラム__ の __グローバル変数やグローバル関数__ に、インポートするモジュールの名前と __同じ名前を使いたい__ 場合

上記の 1 つ目の場合の具体例を挙げます。下記のプログラムは、marubatsu というモジュールを、__maru と batsu の頭文字__ をとって __mb という名前__ でインポートしています。そのため、2 行目では、__`mb.initialize_board()`__ のように、モジュールの関数呼び出しを __短く記述__ することが出来ます。

```python
import marubatsu as mb
board = mb.initialize_board()
print(board)
```
実行結果
```
[[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
```

他の具体例として __numpy__ という非常に良く使われるモジュールをインポートする際には、__`import numpy as np`__ のように __np という名前__ でインポートするのが __一般的__ です。

上記の 2 つ目の場合について補足します。先ほど説明したように、`import モジュール名` でモジュールをインポートした場合、インポートした __モジュールの名前__ が、__グローバル名前空間に登録__ されます。従って、インポートしたモジュールの名前と __同じ名前__ の __グローバル変数__ や __グローバル関数__ を __使いたい場合__ は、モジュールを別の名前でインポートする必要があります。

### 必要な名前だけをインポートする方法

モジュールの中の、__必要な変数や関数だけを利用__ したい場合は、以下のように記述します。この場合も __半角の `,` で区切って__ 記述することで、__複数の名前をインポート__ することが出来ます。

```python
from モジュール名 import モジュール内の名前
```

この方法は、以下のような性質を持ちます。

* インポートした __モジュール内の名前__ は、プログラム内で先頭に `モジュール名.` を __つけずにそのまま__ 利用できる
* インポートした __モジュール内の名前__ は、メインモジュールの名前空間に登録されるので、__グローバル変数__ または、__グローバル関数__ になる
* 上記の方法で __インポートした以外__ のモジュール内の __名前を使用__ することは __できない__

:::note warn
この方法は、他のモジュールで定義された関数を、__モジュールの名前を先頭に付けず__ に、メインモジュールで定義された関数 __のように短く記述できる__ ので、良く使われます。
:::

下記のプログラムは、marubatsu というモジュールの中の、__`initialize_board`__ という名前 __のみ__ をインポートしています。この場合は、2 行目のように、__単に `initialize_board`__ と記述するだけで、この関数を利用することが出来ます。

marubatsu.py の中には他にも __`place_mark` が定義__ されていますが、1 行目で __`place_mark` がインポート__ されて __いない__ ため、4 行目のように、`place_mark` を記述すると、`place_mark` という名前が定義されていないことを表す __NameError が発生__ します。

```python
from marubatsu import initialize_board
board = initialize_board()
print(board)
place_mark(board, 0, 1, "〇")
```
実行結果
```
[[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\017\marubatsu.ipynb セル 11 line 4
      2 board = initialize_board()
      3 print(board)
----> 4 place_mark(board, 0, 1, "〇")

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

この方法は、下記のプログラムの 2 行目のように、marubatsu をインポートしてから、__グローバル変数 `initialize_board`__ に __marubatsu のモジュールのグローバル関数 `initialize_board` を代入__ する __ような__ 処理が行わていると考えると __わかりやすい__ でしょう。

```python
import marubatsu
initialize_board = marubatsu.initialize_board
```

ただし、上記のプログラムと __`from marubatsu import initialize_board`__ は __同じ処理__ を行っている __わけではありません__。上記のプログラムの場合は、1 行目で __`marubatsu` というグローバル変数__ に  __marubatsu というモジュールが代入__ されるため、下記のプログラムの 5 行目のように __`marubatsu.place_mark`__ を記述して __`place_mark` を利用__ することが出来ます。

```python
import marubatsu
initialize_board = marubatsu.initialize_board
board = initialize_board()
print(board)
marubatsu.place_mark(board, 0, 1, "〇")
```
実行結果
```
[[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
```

一方、__`from marubatsu import initialize_board`__ の場合は、__グローバル名前空間__ に __`marubatsu`__ という名前は __登録されていない__ ので、下記のプログラムのように __`marubatsu.place_mark`__ を実行すると __NameError が発生__ します。

なお、下記のプログラムは __JupyterLab を再起動__ してから実行して下さい。

```python
from marubatsu import initialize_board
board = initialize_board()
print(board)
marubatsu.place_mark(board, 0, 1, "〇")
```
実行結果
```
[[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\017\marubatsu.ipynb セル 14 line 4
      2 board = initialize_board()
      3 print(board)
----> 4 marubatsu.place_mark(board, 0, 1, "〇")

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

## github での本記事のモジュールの保存について

本記事では、以降は __marubatsu.py というファイル__ に〇×ゲームに関する __関数の定義など保存し__、__必要な関数をインポート__ してプログラムを記述することにします。

marubatsu.py は __今後の記事__ で __内容を修正したり増やしたり__ しながら __更新__ します。そこで、github の marubatsu.py は、__前回の記事までの内容__ とし、その記事で __新しく marubatsu.py に追加した内容__ は、__marubatsu_new.py という ファイルに保存__ することにします。従って、__marubatsu_new.py__ の内容は、__その次の回の記事の marubatsu.py__ と同じになります。

github の __marubatsu.py__ は各回の記事で更新された結果、__毎回異なる内容__ になるので、今後の記事で marubatsu.ipynb をコピーせずに __自分で入力して実行する__ 場合は、github の __同じフォルダ内の marubatsu.py を毎回コピー__ し直してから実行してください。

なお、記事の中で marubatsu.py に対して __更新する内容が存在しない__ 場合は、__marubatsu_new.py__ というファイルは github には __保存しません__。

# 関数の引数と返り値のデータ型のヒント（型アノテーション）の表記

__関数呼び出しを記述する際__ に、__実引数__ に __どのようなデータ型__ のデータを記述すればよいかが __わかる__ と、関数呼び出しを __正しく記述しやすく__ なり、プログラムの __バグを減らす__ ことにつながります。また、同様の理由で __関数の返り値のデータ型__ がわかると便利です。

Python では、__関数の定義__ を行う際に、__仮引数のデータ型__ と、__返り値のデータ型__ の __ヒント__ を以下のような方法で記述することが出来ます。

このデータ型のヒントの表記の事を、__型アノテーション（annotation）__ と呼びます

```python
def 関数名(仮引数: 仮引数のデータ型) -> 返り値のデータ型:
    関数のブロックのプログラム
```

型アノテーションの詳細については、下記のリンク先を参照して下さい。

https://docs.python.org/ja/3/library/typing.html?highlight=%E3%82%A2%E3%83%8E%E3%83%86%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3

具体例を説明する前に、Python の __データ型の表記__ について説明する必要があるので説明します。

## Python のデータ型の英語表記

本記事ではこれまで、__数値型や文字列型__ などは、__日本語でデータ型を表記__ してきましたが、関数の定義で __型アノテーション__ を __記述する場合__ は、__英語__ でデータ型を __表記する必要__ があります。

また、本記事ではこれまで __数値__ を表すデータを __数値型と表記__ してきましたが、数値型には __整数__ を表す __int__、__浮動小数点数__（小数点以下の数値を含むような数値の事）を表す __float__、__複素数__ を表す __complex__ の 3 種類があり、プログラム内では __それらの英語表記__ を記述する __必要__ があります。

下記は、Python の代表的なデータ型の、日本語表記と英語表記の __対応表__ です。データが存在しないことを表す None 型のみ、頭文字が大文字である点に注意して下さい。なお、本記事でまだ説明していないデータ型については、必要になった時点で説明します。

| 日本語 | 英語 |
|:--|:--|
| 整数型  | int  |
| 浮動小数点数型  | float  |
| 複素数型  | complex  |
| 文字列型  | str  |
| 論理型  | bool  |
| リスト型 | list |
| タプル型 | tuple |
| 辞書型 | dict |
| 集合型 | set |
| None 型  | None  |

なお、今後も __記事の中__ では、整数型と浮動小数点数型を __区別する必要がない__ 場合は、引き続き __数値型__ のように表記します。また、文字列型のデータも str ではなく、__日本語で表記__ します。

## list の場合の型アノテーションの記述方法

:::note alert
Python の __3.8 以前のバージョン__ で下記のような記述を行うと __エラーになります__。

バージョン 3.7 と 3.8 では、`from __future__ import annotations` を記述することで、下記のプログラムを実行することが出来るようになります。

それ以前のバージョンでは別の方法で記述する必要がありますが、Python を 3.9 以降にバージョンアップしたほうが早いと思いますので、その方法については省略します。
:::

仮引数や返り値の型アノテーションが __list__ の場合、その list の __要素のデータ型__ のヒントがあると便利です。そのような型アノテーションは、__`list[データ型]`__ のように表記します。例えば、`[1, 2, 3]` のような、__整数型の要素__ を持つ list の場合は __`list[int]`__ のように表記します。

ゲーム盤のデータを表す __2 次元配列の list__ は、その __要素は list__ を、その __要素の要素__ はマークを表す __文字列型__ のデータ持つので、その場合は __`list[list[str]]`__ のように表記します。

## 記述例

下記は、`initialize_board` と `place_mark` に対して型アノテーションを記述したプログラムです。

```python
def initialize_board() -> list[list[str]]:
    return [[" "] * 3 for x in range(3)]

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

`initialize_board()` は __文字列型の要素を持つ 2 次元配列の list__ を __返り値__ として返す関数なので、上記のように __`-> list[list[str]]`__ が __返り値__ の __型アノテーション__ として記述されています。`place_mark` の仮引数 `board` についても同様です。

`place_mark` の仮引数 `x` と `y` は __マスの座標を表すデータ__ なので、__整数型を表す int__ が型アノテーションとして表記されています。

`place_mark` の仮引数 `mark` は __マークを表すデータ__ なので、__文字列型を表す str__ が型アノテーションとして表記されています。


`place_mark` のように、__返り値を返さない関数__ の場合は、上記のように、関数の返り値の型アノテーションは一般的に __省略__ します。何らかの理由で、返り値を省略しない場合は、`-> None` を記述します。

## 型アノテーションを記述するメリット

関数の定義で型アノテーションを記述することで、以下のようなメリットを得ることが出来ます。

### 関数呼び出しを記述する際に型アノテーションが表示される

VSCode では、__関数呼び出しを記述する際に__、__その付近__ に仮引数のデータ型や、関数の返り値のデータ型の __型アノテーション__ が __表示__ されます。

下図は、VSCode で `place_mark` の関数呼び出しを __記述し始めた場合__ の図です。図のように、__これから入力__ しようとしている __実引数に対応する__ 仮引数 `board` の __型アノテーション__ が、すぐ上に __青い文字で表示__ されています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/73626e58-903e-2395-fc37-f13a6629d254.png" width="500">

### 記述済の関数名にヒントを表示できる

VSCode では、__記述済の関数名の上にマウスカーソルを移動__ すると、その付近に関数に関する __ヒントが表示__ されます。

下図は、VSCode で、`place_mark` の上にマウスを移動した場合の図です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/725dcdb4-0a3f-b9a4-92fe-04e8fab8d5d4.png" width="200">


## 型アノテーションに関する注意点

型アノテーションのことを、データ型の __ヒント__ のように記述してきた理由は、データ型の記述はあくまで __ヒントに過ぎず__、__強制力を持たない__ からです。__型アノテーション__ と __異なる__ データ型の __実引数__ を記述して関数を呼び出したり、__型アノテーション__ と __異なる__ データ型の __返り値__ を return 文で返しても、__エラーは発生しません__。この __型アノテーションの情報__ は、あくまで __目安にすぎない__ ことを忘れないで下さい。

具体例を挙げます。下記のプログラムで定義された `add` は、仮引数 `x` の型アノテーションには整数型の int、`y` の型アノテーションには浮動小数点数型の float、返り値の型アノテーションには文字列型の str が記述されています。

しかし、4 行目のように、__型アノテーション__ と __異なる__ データ型の __実引数__ を記述し、__型アノテーション__ と __異なる__ データ型の __返り値__ が返されても __エラーにはなりません__。

```python
def add(x: int, y: float) ->str:
    return x + y

print(add(0.5, 1))
```
実行結果
```
1.5
```

:::note warn
型アノテーションは目安に過ぎませんが、__型アノテーション__ に __従った__ データ型を __記述__ することは、__プログラムのバグを減らす__ ことにつながります。__関数を定義__ する場合は、__なるべく型アノテーションを記述__ し、__関数を利用__ する場合は特別な理由がない限り、__型アノテーション__ に __従った__ データ型を __記述するべき__ です。
:::

# docstring

上記で紹介した方法では、関数の仮引数や返り値の型アノテーション __しか__ 表示されません。関数呼び出しを記述する際に、関数の「__仮引数の意味__」、「__関数が行う処理__」、「__関数の返り値の意味__」などの、__より詳細なヒント__ が表示されれば、関数呼び出しのプログラムをより __記述しやすくなります__。

そのようなヒントは、関数の定義の `def 関数名(仮引数):` の __直後の行__ に、関数の説明を表す __docstring__ と呼ばれる __文字列を記述__ することで表示することが出来るようになります。

## トリプルクオートによる文字列のリテラル

docstring では __文字列__ を使って、__複数行にまたがって__ 関数の説明を __記述__ しますが、これまで使ってきた半角の `'`（シングルクオート）や `"`（ダブルクオート）で文字列を囲うという文字列のリテラルでは、その間で __改行を行うことはできません__。

下記のプログラムは、`a` という変数に、__改行が含まれた文字列__ を代入しようとした結果、__エラーが発生__ しています。

```python
a = "これは改行が
含まれた文字列です"
print(a)
```
実行結果
```
  Cell In[4], line 1
    a = "これは改行が
        ^
SyntaxError: unterminated string literal (detected at line 1)
```

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

* __SyntaxError__
構文（文法のこと）（syntax）に関するエラー
* __unterminated string literal (detected at line 1)__
文字列（string）のリテラル（literal）が行内で終了していない（unterminated）
:::

Python には、__トリプルクオート__ という、__`'`__ または __`"`__ を __3 つ並べた__ __`'''`__ または __`"""`__ を使って __文字列を囲う__ という __リテラル__ があります。

:::note
シングル __クオート__ である `'` または、ダブル __クオート__ である `"` を __3 つ並べる__ ので __トリプル__ クオート（日本語では、三重引用符または三連引用符）と呼びます。トリプルクオートという 1 つの文字が __存在するわけではない__ 点に注意して下さい
:::

トリプルクオートを使ったリテラルには以下のような特徴があります。

* __改行を含める__ ことが出来る
* __単独の `'`__ や __`"`__ を __文字列の中に含める__ ことが出来る

トリプルクオートによる文字列のリテラルでは、文字列を囲う `'''` または `"""` の __直後__ から、__同じトリプルクオート__ が記述されるまでの __間を文字列とみなします__。従って、文字列を囲うトリプルクオートと __区別できる__ ので、文字列の中に __単独の `'`__ や __`"`__ を __記述することが出来ます__。同様の理由でクオートが __2 つ連続__ した `''` や `""` も記述することができます。

:::note
トリプルクオートが文字列を囲う文字として採用された理由は、一般的に __文字列の中__ に `'''` や `"""` が __含まれる__ 場合が __ほとんどない__ ためです。
:::

下記のプログラムは、__`"""`__ を使って改行が含まれた文字列を `a` に代入しています。

```python
a = """これは改行が
含まれた文字列です"""
print(a)
```
実行結果
```
これは改行が
含まれた文字列です
```

## docstring のスタイル

docstring の書き方には __いくつかのスタイル__ （流儀）があります。以下に、代表的な docstring のスタイルのドキュメントのリンクを紹介します。なお、他にも docstring のスタイルは存在します。

どのスタイルを利用するかは、__自分の好み__ に合わせて __選択__ してかまいませんが、同じプログラムの中では docstring の __スタイルを統一__ することを __強くお勧めします__。本記事では __Google Style__ で docstring を記述することにします。

### PEP257 

https://peps.python.org/pep-0257/

### Google Style

https://google.github.io/styleguide/pyguide.html#s3.8.1-comments-in-doc-strings

### Numpy Style

https://numpydoc.readthedocs.io/en/latest/format.html#sections

## Google Style の docstring の書き方

関数に対する[^1] docstring は以下のように記述します。Google Style では、トリプルクオートに __`"""`__ を使用します。

```
""" 関数の概要

詳細な関数の説明

Args:
    仮引数の名前（仮引数のデータ型）:
        仮引数の説明

Returns:
    返り値のデータ型: 返り値の説明
"""
```

上記の `Args:` のような部分のことを __セクション__ と呼びます。セクションには上記の Args や Returns 以外にも、__関数の使用例__ を記述する __Examples__ や、__注意事項や注釈__ を記述する __Notes__ などがあります。それ以外のセクションについては必要になった時点で説明します。

セクションは __必要がなければ記述する必要はありません__。例えば仮引数が存在しない関数の場合は Args を、返り値を返さない関数の場合は Returns のセクションを記述する必要はありません。

他にも、以下のようなルールがあります。docstring を長く記述するとわかりづらくなるので、できるだけ __簡潔に記述する__ ことになっています。下記のルール 1、4、6、7 はそのことを表しています。

1. 関数の概要は基本的に 1 行で記述する
2. 関数の概要の最後には半角の `.`、`?`、`!` のいずれかを記述する
3. 関数の概要、詳細説明、セクションなどの __間__ は、__1 行分の空行__ で間を __空ける__
4. __説明の必要がないほど簡単な関数__ には、関数の概要のみを記述した、__1 行の docstring__ を記述する
5. __1 行__ に記述する長さは __最大で 80 文字__ とする（全角の場合は 40 文字）
6. 関数の定義に型アノテーションを記述した場合は、仮引数や、返り値の __データ型__ は __省略する__
7. docstring 説明には、__関数の機能や使い方__ に関する記述を行う。関数の中で行われる __具体的な処理の手順__ については __記述しない__

:::note warn
docstring はその __関数を利用する人__ に向けて記述された __取扱説明書のようなもの__ なので、__関数の使い方に関する説明だけ__ を記述するべきです。これは、一般の家電製品の取扱説明書の中に、中に入っている機械の説明が記述されていないのと同様です。

関数の中で行われる __具体的な処理__ についての説明を記述したい場合は、docstring ではなく、__`#` によるコメントで記述__ します。 
:::

下記は、`initialize_board` と `place_mark` に docstring を記述したプログラムです。なお、上記のルール 6 で説明したように、__関数の定義の中__ で仮引数や返り値の __型アノテーションを記述している__ ので、docstring では仮引数や返り値の __データ型は記述しません__。

```python
def initialize_board() -> list[list[str]]:
    """ 初期化されたゲーム盤のデータを返す.

    Returns:
        初期化されたゲーム盤を表す 2 次元配列の list.
        全ての要素に半角の空白文字が代入されている.
    """

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

def place_mark(board: list[list[str]], x: int, y: int , mark: str):
    """ ゲーム盤の指定したマスに指定したマークを配置する.

    (x, y) のマスに mark で指定したマークを配置する.
    (x, y) のマスに既にマークが配置済の場合は、メッセージを表示する.

    Args:
        board:
            マークを配置する、ゲーム盤を表す 2 次元配列の list
        x:
            マークを配置するマスの x 座標
        y:
            マークを配置するマスの y 座標
        mark:
            配置するマークを表す文字列
    """

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

:::note
docstring は本来は __英語で記述することが推奨__ されますが、本記事では __わかりやすさを重視__ して __日本語で記述__ します。
:::

## docstring を文字列で記述する理由

docstring を `#` によるコメントではなく、文字列で記述する点を不思議に思っている人がいるかもしれないので補足します。

__データだけが記述された文__ を実行した場合は __何の処理も行われません__。例えば、下記のプログラムは、1 行目で `"a"` という文字列型のデータだけを記述していますが、実行しても何も __処理は行われません__。

```python 
"a"
```

従って、docstring のように、__複数の行にまたがって文字列のリテラルだけを記述__ した場合、何も処理は実行されないので docstring を __コメントのように利用__ することが出来ます。

:::note
実際には、上記のプログラムの場合は `"a"` を管理する __新しいオブジェクトが作られます__ が、そのオブジェクトは __どの変数からも参照されていない__ ので、その後のプログラムで __利用することはできません__。従って、__実質的には何の処理も行われていません__。
:::

上記で、データだけが記述された文を実行しても何の処理も行われないと説明しましたが、__関数のブロックの先頭の行__ に __文字列のリテラルだけを記述__ した場合は例外的に、その __文字列のデータ__ がその関数の定義を管理するオブジェクトの __データに登録__ され、この後で説明する組み込み関数 __`help`__ を使うことで __表示することが出来る__ という仕組みになっています。

__コメント__ は Python のデータとして認識されないので、コメントに記述した内容を __オブジェクトが管理__ することは __ありません__。そのため、__`help` のような仕組み__ を利用することが __出来ない__ ので、docstring は __文字列で記述する必要__ があります。

## docstring を記述するメリット

docstring を記述することにって、以下のようなメリットがあります。

### プログラムがわかりやすくなる

関数に docstring を記述することで、__関数の定義がわかりやすくなる__ というメリットがあります。

### help を利用できる

__`help`__ という組み込み関数の __実引数__ に、__docstring を記述した関数名__ を記述して呼び出すことで、その関数に記述された __docstring を表示する__ ことが出来ます。`help` を利用することで、関数の定義の __プログラムを見なくても__、その関数の __使い方がわかる__ ようになります。

```python
help(initialize_board)
```
実行結果
```
Help on function initialize_board in module __main__:

initialize_board() -> list[list[str]]
    初期化されたゲーム盤のデータを返す.
    
    Returns:
        初期化されたゲーム盤を表す 2 次元配列の list.
        全ての要素に半角の空白文字が代入されている.
```

:::note
実行結果の 1 行目のメッセージの意味は、「メインモジュール（module \_\_main\_\_）内（in）の initialize_board という関数（function）の（on）ヘルプ（Help）」です。
:::

### 関数呼び出しを記述する際に docstring が表示される

VSCode では、docstring が記述された __関数呼び出しを記述__ すると、その付近に __docstring が表示__ されます。下図は VSCode で `place_mark` を記述した場合の図です。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/f499e23b-ee2f-58ef-184e-3f01147c4222.png" width="500">

### 関数の上にマウスを移動すると docstring が表示される

VSCode では、__関数名の上にマウスを移動__ すると、その付近に __docstring が表示__ されます。表示される内容は、上図と同様なので図は省略します。

### pydoc を使ってドキュメントを作ることができる

本記事では紹介しませんが、__pydoc__ を使って、docstring に記述した内容を元に、プログラムで定義した関数の使い方の __ドキュメントを作成__ することが出来ます。興味がある方は pydoc をキーワードに調べてみると良いでしょう。参考までに、pydoc に関するページのリンクを下記に紹介しますが、このドキュメントは初心者にはわかりづらいので、別の pydoc の使い方を紹介するページを見たほうが良いかもしれません。

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

## 本記事での docstring の記述について

docstring は一般的に、__関数が完成してから記述__ するものです。その理由は、関数の作成途中に docstring を記述した場合に、関数を修正するたびに docstring を __書き直す__ 必要があるからです。

本記事でも、docstring 記事の途中では記述せず、記事で行われた変更をまとめた marubatsu_new.py のほうで記述することにします。

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

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

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

以下のリンクは、今回の記事で更新した marubatsu_new.py です。

https://github.com/ysgeso/marubatsu/blob/master/017/marubatsu_new.py

# 次回の記事

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

# 更新履歴

| 更新日時 | 更新内容 |
|:--|:--|
| 2023/11/25 | データ型のヒントを、型アノテーションに修正しました |
| 2023/10/09 | `set_mark` を `place_mark` に修正しました<br>`for y in range(3)` を `for x in range(3)` に修正しました |



[^1]:関数以外に対する docstring の書き方については、必要になった時点で説明します