Pythonで〇×ゲームのAIを一から作成する　その7　Python のデータとオブジェクト

# 目次と前回の記事

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

https://qiita.com/ysgeso/items/2a436b3819bef9a6a2b4

# 前回のおさらい

前回の記事では繰り返し処理を使って、初期化されたゲーム盤を作成する方法を紹介しました。

しかし、記事の最後で紹介した下記のプログラムは、(1, 0) のマスを表す要素に `"〇"` を代入すると、(0, 0) と (2, 0) を表す要素にも __同時に `"〇"` が代入__ されてしまうという __重大な欠陥__ があります。以後は、このプログラムの事を、__欠陥のあるゲーム盤を初期化するプログラム__ と表記します。

```python
board = [[" "] * 3] * 3
board[1][0] = "〇"
print(board)
```
実行結果
```
[['〇', ' ', ' '], ['〇', ' ', ' '], ['〇', ' ', ' ']]
```

なぜこのような現象が起きるかについて __正しく理解する__ ことは、Python のプログラミングを行っていく上で __非常に重要__ で、__避けて通ることはできません__。

そこで、今回からしばらくの間の記事は、〇×ゲームの実装を一旦中断し、このような現象が起こる理由について詳しく説明することにします。

:::note
今回からしばらくの間の記事は、非常に重要な内容ではありますが、__初心者には難しい__ かもしれません。どうしても意味がわからない場合は、今の段階ではざっと目を通しておいて、先に進んでも良いと思います。

今はわからなくても、Python のプログラミングに慣れてくれば、徐々に意味が分かってくるようになると思いまので、意味が分かるようになったと思った段階で、後から読み直して理解することをお勧めします。

逆に、ある程度プログラミングを学んだ方にとっては既に知っていることかもしれません。そのような場合は復習の意味でざっと目を通し、知らない内容があればその部分だけじっくりと読めば良いでしょう。
:::

# 一見すると奇妙に見えるプログラム

下記のプログラムで行われている処理の内容をいきなり説明するのは難しいので、先に __類似する現象__ が発生する、より __簡単な__ プログラムを使ってこの現象の説明をすることにします。

```python
board = [[" "] * 3] * 3
```

プログラミングの初心者にとってわかりづらい例として、以下のようなプログラムの例が良く挙げられます。

下記は、以下のような処理を行うプログラムです。このプログラムを以降は、__プログラム A__ と表記します。

* 変数 `a` に `5` を代入する
* 変数 `b` に変数 `a` を代入する
* 変数 `a` に `2` を代入する
* 変数 `a` と `b` を表示する

```python
a = 5
b = a
a = 2
print(a)
print(b)
```
実行結果
```
2
5
```

ほとんどの人が想像するとおり、`b` に `a` を代入した後で、`a` の値を変更しても、`b` の値は変化しません。

一方、下記のプログラムは、以下のような処理を行うプログラムです。このプログラムを以降は、__プログラム B__ と表記します。

* 変数 `a` に `[1, 2, 3]` という list を代入する
* 変数 `b` に変数 `a` を代入する
* 変数 `a` の 0 番の要素に `2` を代入する
* 変数 `a` と `b` を表示する

```python
a = [1, 2, 3]
b = a
a[0] = 2
print(a)
print(b)
```
<details><summary><strong>プログラム A と B の違い</strong></summary>

```diff_python
- a = 5
+ a = [1, 2, 3]
b = a
- a = 2
+ a[0] = 2
print(a)
print(b)
```
</details>

実行結果
```
[2, 2, 3]
[2, 2, 3]
```

プログラム B は `b` に `a` を代入した後で、`a` の 0 番の要素に値を代入して変更すると、実行結果から `b` の値も __同時に変更__ されているように見えます。

プログラム A と プログラム B は、どちらも __同じような代入文__ の処理を 3 回行うプログラムですが、実行すると結果が大きく異なっているように __見えます__。初心者にとって プログラム B は __奇妙な処理__ が行われているように思えるかもしれませんが、実際には __正しい処理__ が行われています。

プログラム B で起きるこの現象は、欠陥のあるゲーム盤を初期化するプログラムで作られたゲーム盤の (1, 0) のマスを表す要素に `"〇"` を代入すると、複数の要素が __同時に変更__ されるように __見える__ 現象とよく似ています。実際に、どちらも __同じ理由__ でそのような現象が起きています。

プログラム B のほうが、欠陥のあるゲーム盤を初期化するプログラムより簡単なプログラムなので、__先にプログラム B の説明__ を行ってから、欠陥のあるゲーム盤を初期化するプログラムについて説明します。

# Python のデータとオブジェクト

プログラム A、B で行われている処理の違いを正しく理解するためには、Python がデータをどのように扱っているかの __仕組み__ を知る必要があります。

Python では 数値型、文字列型、list などの、様々なデータ型を扱うことができますが、それらは __全てオブジェクト__ という形式で記録されています。オブジェクトは Python のプログラミングにおける非常に重要な概念ですが、かなり複雑なものです。本記事では、オブジェクトに関しては一度にすべてを説明せずに、必要に応じて少しずつ解説していく予定です。

今回の記事では、数あるオブジェクトの機能のうち、__データを管理する__ という機能に絞って説明します。

:::note
データがどのように扱われるかについては、__プログラム言語によって異なる__ 場合があります。本記事は __Python に関する説明__ なので、他のプログラム言語には当てはまらない可能性がある点に注意して下さい。
:::

オブジェクトは、Python が扱う __データを管理__ するための __入れ物__ のようなもので、Python では __全てのデータ型__ が、オブジェクトという __共通の形式__ で管理されます。

:::note
オブジェクトは、英語では「物」、「物体」を表す object という単語で、Python などのプログラミング言語だけでなく、コンピュータでは __良く使われる__ 用語です。例えば Microsoft Word では、図形や画像などをオブジェクトと呼び、Word のホームタブの編集グループには「オブジェクトの選択」というメニューがあります。

ただし、同じオブジェクトという用語が使われていますが、その意味は __異なる場合が多い__ ので注意する必要があります。実際に、Python などのプログラム言語のオブジェクトと、Word のオブジェクトは、意味が全く __異なります__。
:::

## オブジェクトの id（識別子）

Python では、プログラムが扱う __データごと__ にオブジェクトが __作られる__ ので、大量に作られたオブジェクトどうしを __識別__（区別）する必要があります。そのためオブジェクトには、オブジェクトを __識別__ するための __整数__ の __id__（識別子）が付けられます。また、オブジェクトは、オブジェクトの id を使って __参照__ されます。id はオブジェクトを識別するためのものなので、すべてのオブジェクトに対して __必ず異なる id__ が付けられます。

:::note
オブジェクト id は Python が __自動的につける__ ものなので、プログラムを記述する際に、オブジェクトの id の __管理を行う必要はありません__。
:::

:::note warn
Python などのプログラム言語では、__様々な種類の__ 識別子が用いられます。例えば、変数名は、変数に代入されたデータを参照するための識別子です。また、list はインデックスという識別子を使って、list の要素を参照します。そのため、識別子に関する __用語__ が出てきた場合は、何を __識別__ しているかについて意識するようにしないと、誤解が発生する可能性がある点に注意して下さい。
:::

## 用紙の例え

オブジェクトをプログラミング言語の概念だけで説明するとわかりづらいと思いますので、現実の世界の「__用紙__」で例えながら説明することにします。ここでいう「用紙」は以下のような性質を持つものだと考えてください。

* __好きな内容__ を書くことができる
* 必要に応じて __何枚でも__ 新しい用紙を用意できる
* 全ての用紙には、__異なる識別番号__ があらかじめ記入されている
* 識別番号を後から __変更することはできない__
* 識別番号を使って、特定の用紙を __探し出す__ ことができる
* 用紙の大きさは決まっておらず、書き込みたい内容に応じて __好きな大きさ__ の用紙を用意できる

この例えでは「オブジェクト」と「用紙」は以下の表のように対応します。

| オブジェクト | 用紙 |
|:--|:--|
| オブジェクトが管理するデータ  | 用紙に書かれた内容  |
| id  | 識別番号  |
| 新しいオブジェクトの作成 | 新しい用紙を用意する |

:::note
ものごとをわかりやすく説明するために、似ているものに例えて説明することが良く行われますが、例えたものと、例えられたものが __全く同じ__ であるということは __ほとんどありません__。例え話にはどうしても __正確性が欠けてしまう__ という欠点があるので、例え話はものごとの __概要を把握__ するためのものだと考えて下さい。そのため、ものごとを __正確に__ 理解するためには、概要を把握した後で __例えに頼らずに__ 正確な知識を身に付ける必要があります。

「用紙」の例えの場合は、オブジェクトの「データを管理する」という側面だけを考えて例えています。今後説明する予定のオブジェクトの他の性質を「用紙」で例えることはできません。
:::

## オブジェクトの id の取得

Python では、__`id`__ という組み込み関数を使って、`()` の中に記述したデータを管理するオブジェクトの id を __取得する__ ことができます。2 つのデータを管理するオブジェクトの id を調べて __比較する__ ことで、2 つのデータを __同じオブジェクトが管理__ しているかどうかを __調べる__ ことができます。

下記のプログラムは、数値型、文字列型、list のデータを管理するオブジェクトの id を表示しています。実行結果から、3 つの異なるデータに対して、そのデータを管理するオブジェクトの id が __すべて異っている__ ことが確認できます。

```python
print(id(1))
print(id("abc"))
print(id([1, 2, 3]))
```
実行結果（実際に表示される数値は、下記と __異なる場合__ があります）
```
140731432669992
140731431470144
1706072311232
```

## オブジェクトの図示

言葉だけではわかりづらいので、以後はオブジェクトを表す図を使って説明します。下図は、上記のプログラムで記述した 3 つのデータを管理するオブジェクトを、以下のようなルールで図示しています。

* オブジェクトを __薄いオレンジ色の用紙__ のような図形で表現する
* オブジェクトの id を __図形の上部__ に表示する
* オブジェクトの id を表す数値を __赤色の文字__ で表示する
* オブジェクトが管理するデータを図形の中の __白い長方形の中__ に表示する
* オブジェクトが管理するデータを __黒色の文字__ で表示する

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/a3d5c859-8d14-fa2f-1322-8b20029b7c98.png" width="350" title="aaa">

:::note
オブジェクトの id は Python が他のオブジェクトと __重複しない__ ように自動的に割り当てるので、新しく Python が作成したオブジェクトの id が何になるかは __プログラムを実行してみなければわかりません__。そのため、上記のプログラムを実行した時に表示される id は、__プログラムを実行するたびに変わる__ 可能性があります。

また、実際に付けられる id は上記のプログラムの実行結果を見ればわかるように、__かなり大きな数字__ になることが多く、それを図でそのまま表示するとかなりの場所が必要になってしまいます。

そこで、図ではオブジェクトの id として __筆者がその場で思いついた__ 適当な __3 桁の数字__ を設定しています。図に表示される id に __深い意味は全くありません__ ので、図の id の数字に何か意味があるのではないかと __勘違いしないよう__ にして下さい。
:::

## オブジェクトの作成とリテラル

`1`、`"abc"`、`[1, 2, 3]` などのように、__プログラム内に直接記述されたデータ__ の事を、__リテラル__ と呼びます。リテラルは、リテラルが記述されているプログラムの内容を書き替えない限り、__常に同じデータ__ を表すので、__プログラムの実行中__ に別のデータに __変化することはありません__。

一方、`a` のような __変数__ は、代入文によって __プログラムの実行中に内容が変化__ します。

:::note warn
Python では、__リテラルが記述された文が実行__ されるたびに、そのリテラルを管理する __新しいオブジェクト__ が作成されます。一方、変数は既にその中に __データが代入済__ なので、変数が記述された文が実行されても、その変数に代入された値に対応する新しいオブジェクトは __作成されません__。
:::

リテラルが記述された文を実行する際に、そのリテラルが過去に作成されたオブジェクトが管理するデータと __同じ内容__ であったとしても、__新しいオブジェクト__ が作成されます。

:::note
実際には、一部例外（後述します）があるようですが、リテラルが記述された文を実行するたびに、__必ず新しいオブジェクトが作成される__ と理解しても __問題はありません__。
:::

具体例を挙げて説明します。下記は、`1.2` という数値型のデータを __2 回記述__ し、そのデータを管理する __オブジェクトの id を表示__ するプログラムです。下記のプログラムを実行すると、全く同じ `1.2` というデータであるにも関わらず、__2 つの異なった__ `1.2` というデータを管理するオブジェクトが作成されます。そのことは、実行結果で __異なる id__ が表示されることから確認できます。

```python
print(id(1.2))
print(id(1.2))
```
実行結果（OS や Python のバージョンなどによっては、__同じ id が表示される__ 可能性があります）
```
1706069801744
1706069799280
```

このことを図で表すと下図のようになります。下図は、同じ `1.2` というデータを管理する、__異なる id__ を持つオブジェクトが __2 つ作成__ される様子を表します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3524802/8c34a813-2051-c70a-85bc-77e66aa06580.png" width="200">


同じ数値型のデータを表すオブジェクトが複数作られたとしても、計算を行う際に __問題が起きることはありません__。わかりづらいかもしれませんので用紙で例えて説明します。

Python のプログラムで `1.2 + 1.2` という式を記述して実行した場合、`1.2` という数値型のデータが 2 回記述されているので、先ほどと同様に、数値型の `1.2` というデータを管理する __異なる 2 つのオブジェクト__ が作成されます。これを用紙で例えると、__2 枚の新しい用紙__ を用意し、それぞれに __同じ__ `1.2` という数字を __書きこむ__ ことに相当します。

2 枚の用紙は __異なる用紙__ ですが、計算は用紙に __書かれている__ 同じ `1.2` という数字を使って行うため、問題なく計算を行うことができます。大事なのは、用紙そのものではなく、用紙の中に書かれている __内容__ であるということです。

このことは、オブジェクトでも同様で、`1.2 + 1.2` という計算を行う際に使われるのは、オブジェクトそのものではなく、オブジェクトが __管理する__ `1.2` という __データ__ なので、`1.2` というデータを管理するオブジェクトが複数作られても、__問題なく計算を行う__ ことができるのです。

## 同じデータに対するオブジェクトの使いまわし


先程、同じデータであっても異なるオブジェクトが作られると説明しましたが、下記のプログラムが示すように、数値型のデータや、文字列型のデータの場合は、同じデータに対して同じオブジェクトが割り当てられることもあるようです。

:::note
下記のプログラムの実行結果は、筆者のコンピューターで実行した場合のものです。Python のバージョンや、OS などの違いによって、このプログラムを実行した際に、同じデータに対して異なる id が表示される可能性があります。
:::


```python
print(id(1))
print(id(1))
print(id("abc"))
print(id("abc"))
```
実行結果
```
140731432669992
140731432669992
140731431470144
140731431470144
```

具体的にどのような場合に同じオブジェクトが割り当てられるかの法則については、調べてもよくわかりませんでしたが、このような使いまわしは、__使いまわしを行っても問題が発生しない場合__ にしか行われないので、その具体的な法則を理解することはあまり重要ではありません[^1]。

オブジェクトが使いまわされる理由としては、プログラムの中の計算式で、1 や 0 などの数値型のデータは大量に使われることが多く、毎回そういった数値データが書かれた文を実行するたびに新しいオブジェクトを作るのが __効率が悪い__ ためです。

用紙の例で例えると、同じ内容を様々な場所で扱いたい場合に、同じ内容が書かれた用紙を必要になるたびに毎回新しく用意するのではなく、その内容が書かれた 1 枚の用紙を __使いまわして__ 利用することに相当します。

このようなオブジェクトの使いまわしは、使いまわしを行っても計算結果に影響がない数値型や文字列型のデータで行われることはあるかもしれませんが、使いまわしを行うと間違った計算結果の原因となる list などのデータでは __決して行われません__。どのような場合に使いまわしをしても良いかについては、これまでの記事で説明した知識ではうまく説明できないので、後の記事で説明します。

オブジェクトの id は実際には使いまわしがされることがあるかもしれませんが、プログラムでオブジェクトに関する処理を理解する際には、以下のように考えても問題はありません。

:::note warn
プログラムでデータが記述された文が実行されると、データごとに __異なるオブジェクトが作成される__。
:::

## 今回の記事のまとめ

今回の記事の内容をまとめると以下のようになります


* オブジェクトは __データを管理__ するための __入れ物__ のようなものである
* オブジェクトには、オブジェクトごとに __異なる id__ が付けられている
* リテラルとは、`1` や `"abc"` のように、プログラムの中に直接記述されたデータのことである
* __リテラルが記述__ されたプログラムの文が __実行されるたび__ に、そのデータを __管理する__ オブジェクトが __作成__ される
* __変数が記述__ されたプログラムを実行した際に、その変数に代入されたデータに対して新しいオブジェクトが __作成されることはない__

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

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

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

# 次回の記事

https://qiita.com/ysgeso/items/9b00b35d9901a9793a30

[^1]:そのような法則がわかったとしても、Python のバージョンや種類（Python そのものにもいくつかの種類があります）、Python を実行するコンピュータの OS などによってその法則が異なる可能性があるからです