目次と前回の記事
実装の進捗状況と前回までのおさらい
〇×ゲームの仕様と進捗状況
正方形で区切られた 3 x 3 の 2 次元のゲーム盤上でゲームを行う
ゲーム開始時には、ゲーム盤の全てのマスは空になっている
2 人のプレイヤーが遊ぶゲームであり、一人は 〇 を、もう一人は × のマークを受け持つ
- 2 人のプレイヤーは、交互に空いている好きなマスに自分のマークを 1 つ置く
- 先手は 〇 のプレイヤーである
- プレイヤーがマークを置いた結果、縦、横、斜めのいずれかの一直線の 3 マスに同じマークが並んだ場合、そのマークのプレイヤーの勝利とし、ゲームが終了する
- すべてのマスが埋まった時にゲームの決着がついていない場合は引き分けとする
仕様の進捗状況は、以下のように表記します。
- 実装が完了した部分を
背景が灰色の長方形
で記述する - 実装の一部が完了した部分を、太字 で記述する
これまでに作成したモジュール
以下のリンクから、これまでに作成したモジュールを見ることができます。
前回までのおさらい
前回の記事では、オブジェクト指向プログラミングについて説明し、これまでに作成した関数を Marubatsu
というクラスで定義しなおしました。
クラスとインスタンスの仕組み
前回の記事で、属性、メソッド、クラス、インスタンス などの、オブジェクト指向プログラミングの基本的な用語と簡単な使い方について説明しました。
前回の記事を読んだだけでは、多くの方はクラスとインスタンスの 違い や、それらが 異なったもの として 存在する理由 について理解できないのではないかと思います。
オブジェクト指向プログラミングを行うためには、クラスとインスタンスに関する仕組み について理解する必要があります。また、これらの仕組みの中で、クラスとインスタンスの 名前空間 の仕組みについて理解することが重要です。今回の記事ではそれらについて説明します。
dict
Python の 名前空間 は、list と似た性質を持つ dict という データ型 を使って実現されています。
dict の詳細については下記のリンク先を参照して下さい。
dict は日本語では 辞書(dictionary)型 と呼ばれる、その 中に複数のデータを持つ ことが出来る 複合データ型 のデータで、list とよく似た性質 を持っているので、list の性質と対応させながら説明します。
下記の表は、dict と list の主な違いをまとめたものです。
格納するデータの性質 | list | dict |
---|---|---|
名称 | 要素 | 値(value) |
識別子の名称 | インデックス | キー(key) |
識別子のデータ型 | 整数型のみ | 文字列型など |
記述方法 | listのデータ[インデックス] |
dictのデータ[キー] |
リテラルで囲う記号 |
[ と ]
|
{ と }
|
リテラルでの記述方法 | データをそのまま記述する | キー: 値 |
dict が格納するデータの 識別子 の事を、キー(key)と呼びます。list の識別子である インデックス が 整数しか 使うことが 出来ない のと異なり、dict の識別子である キー には 文字列型、数値型、tuple などを使うことが出来ますが、list、dict をキーとして利用することはできません(エラーが発生します)。dict のキーとしては 文字列型 のデータが 良く使われます。
dict が格納するデータの事を dict の値(value)と呼び、キーに対応するデータの事を、キーの値 と呼びます。キーの値は、dictのデータ[キー]
のように記述します。
list の要素と同様に、プログラムに 記述した dict の値 は 変数と同様の性質 を持ち、任意のデータを代入 したり、式の中で記述 することで dict の値を取り出して利用することが出来ます。
dict のリテラル は、下記のように、半角の {
と }
の間 に キーと値のペア を、間に 半角の :
(コロン)を挟んで キー: 値
のように記述します。複数の キーと値のペアを記述する場合は、半角の ,
で区切り ます。また、list のリテラルの場合と同様に、,
の後などで 改行 することが出来ます。
{}
は、値を一つも持たない dict を表し、そのような dict のことを 空の dict と呼びます。
{ キー1: 値1, キー2: 値2, (必要なだけ キー: 値 を記述できる) }
list と dict の主な違いは、「いくつかの用語」、「識別子が整数であるかどうか」、「リテラルで囲う記号の違い」などで、それ以外の性質は 多くの部分で共通 します。
下記のプログラムは、1 行目で、2 つの値を持つ dict を a
に代入し、2、3 行目で 2 つのキーの値を、4 行目で dict そのものを print
で表示しています。実行結果からわかるように、dict を print
で表示すると、dict の リテラルと同じ形式 で表示が行われます。
a = { "fruits": "apple", "price": 1000 }
print(a["fruits"])
print(a["price"])
print(a)
実行結果
apple
1000
{'fruits': 'apple', 'price': 1000}
dict の キー と、オブジェクトの 属性名 はどちらも 文字列を識別子 にできるので、dict のキーの値を オブジェクト.キー
のように記述できると思う人がいるかもしれませんが、そのような記述をすると下記のプログラムのように エラーになる 点に注意して下さい。
print(a.fruits)
実行結果
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\020\marubatsu.ipynb セル 2 line 1
----> 1 print(a.fruits)
AttributeError: 'dict' object has no attribute 'fruits'
詳細は省略しますが、エラーが発生する原因は、dict のキーが、この後で説明するオブジェクトの名前空間と 異なる方法 で、キーと値の対応づけ を行っているからです。
dict への値の追加
list に要素を 追加 するためには、append
メソッドなどを使う必要がありましたが、dict の場合は、[]
の中にキーを記述 し、代入文 を使って 直接 dict に 値を追加 することが出来ます。
下記のプログラムは、上記のプログラムの続きで、代入文 を使って "color"
という キーの値 に "red"
という文字列を代入しています。実行結果から、dict に新しい "color"
というキーとその値が 追加されている ことを確認することが出来ます。
a["color"] = "red"
print(a["color"])
print(a)
実行結果
red
{'fruits': 'apple', 'price': 1000, 'color': 'red'}
for 文による dict の繰り返し
dict は list と同様に 反復可能オブジェクト なので、for 文による繰り返し処理 で利用できます。
for 文で dict のキーを取り出す繰り返し
for 文の反復可能オブジェクトに dict を記述した場合は、繰り返しのたびに、dict の キー が取り出されて for 文の 変数に代入 されます。下記は、先程のプログラムの続きで、a
に代入された dict の キーを表示 するプログラムです。
取り出されるキーの順番は、dict に値を追加した順番と同じ 2です。dict のリテラルを記述した場合は、空の dict に対して、先頭 のキーと値のペア から順番 に dict に値が追加されます。
先程のプログラムは、リテラルで fruits
、price
のキーと値が、その後の代入文で color
のキーと値が dict に追加されたので、下記のプログラムは その順番 で キーが表示 されます。
for key in a:
print(key)
実行結果
fruits
price
color
dict の キーの一覧 は、下記のプログラムのように、dict のキーの一覧 を返す、keys
という メソッド を使って取得することも出来ます。実行結果に表示される、dict_keys の直後の ()
の中に list のリテラルと同じ形式 で、dict のキーの一覧 が表示されます。
print(a.keys())
実行結果
dict_keys(['fruits', 'price', 'color'])
for 文を使って dict のキーの一覧を利用する場合に keys
メソッドを利用する意味はないでしょう。keys
メソッドの具体的な利用例については必要になった時点で紹介します。
dict_keys は、この後で説明する values
メソッドの返り値である dict_values と同じ性質を持つ ので、詳細はこの後の説明を見て下さい。
for 文で dict の値を取り出す繰り返し
for 文で dict の 値 を利用したい場合は、以下の 2 つの方法があります。
一つ目の方法は、先ほど説明した dict から キーを取り出す繰り返しのブロック の中で、下記のプログラムの 2 行目のように、取り出した キーを使って dict の値を記述 する方法です。
for key in a:
print(a[key])
実行結果
orange
1000
red
もう一つの方法は dict の値の一覧 を返す、values
という メソッド を利用する方法です。
下記のプログラムは、a.values()
を表示することで、a
に代入された dict の値 を 要素として持つ dict_values という 複合データ型 のデータを表示しています。表示結果の dict_values の ()
の中に記述されたデータは、list のリテラルと同じ形式で dict の値 が表示されます。
print(a.values())
実行結果
dict_values(['orange', 1000, 'red'])
dict_values は 反復可能オブジェクト なので、下記のプログラムのように、for 文の反復可能オブジェクト として記述することで、繰り返しの中で dict の値 を 先頭から順 に取り出して for 文の変数に代入することが出来ます。
for value in a.values():
print(value)
実行結果
orange
1000
red
for 文の反復可能オフジェクトとして利用する 場合は、dict_values は list と同じ性質を持つ と考えても構いませんが、それ以外の場合 で dict_values を利用する場合は 注意が必要 です。具体的には dict_values に対して、list のように []
とインデックスを使ってデータを代入したり、データを取り出すことは できません。下記のプログラムを実行するとエラーが発生します。
print(a.values()[0])
実行結果
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\020\marubatsu.ipynb セル 9 line 1
----> 1 print(a.values()[0])
TypeError: 'dict_values' object is not subscriptable
上記のエラーメッセージは、以下のような意味を持ちます。
-
TypeError
データ型(Type)に関するエラー -
'dict_values' object is not subscriptable
dict_values 型のオブジェクトは、[]
を使ってその中のデータを参照する(subscript3)ことはできない(is not able)
dict_values を list のように利用 したい場合は、list の 組み込み型のクラス である list
を利用します。list
に 実引数 を記述して呼び出すと、実引数の値を list に変換した値を返す という処理が行われるので、下記のプログラムのように記述することで、dict_values を list に変換 することができます。なお、先ほどのノートで説明した dict_keys
も同様の方法で list に変換することが出来ます。
組み込み型の変換については前回の記事を参照して下さい。
values = list(a.values())
print(values)
print(values[0])
実行結果
['apple', 1000, 'red']
apple
for 文で dict のキーと値の両方を取り出す繰り返し
dict には、dict の キーと値 を 要素 として持つ tuple4 の一覧 を返す、items
メソッド があります。
下記のプログラムは、a.items()
を表示することで、a
の キーと値 を 要素 として持つ tuple の一覧 を持つ dict_items という複合データ型のデータを表示しています。dict_items は dict_keys や dict_values と 同様の性質を持つデータ型 です。
print(a.items())
実行結果
dict_items([('fruits', 'orange'), ('price', 1000), ('color', 'red')])
dict_items も 反復可能オブジェクト なので、下記のプログラムのように、for 文の反復可能オブジェクトとして記述することで、繰り返しの中で先頭の tuple から順に取り出して for 文の変数に代入することが出来ます。下記のプログラムは、dict の キーと値のペア を要素として持つ tuple を先頭から順に表示します。
for item in a.items():
print(item)
実行結果
('fruits', 'orange')
('price', 1000)
('color', 'red')
tuple の要素 は list と同じ方法 で []
とインデックス を記述して参照することが出来ます。item
に代入された tuple は、0 番 の要素に キー が、1 番 の要素に 値 が代入されているので、下記のようなプログラムを記述することで、キーと値を個別に表示することが出来ます。
for item in a.items():
print("key:", item[0], ", value:", item[1])
実行結果
key: fruits , value: apple
key: price , value: 1000
key: color , value: red
Python では、下記のように記述することで、tuple の 要素 を 別々の変数に代入 する処理を 1 行の文で記述 することが出来ます。下記のプログラムの 1 行目では、x
に tuple の 0 番の要素の値を、y
に tuple の 1 番の要素の値を代入する処理が行われます。
(x, y) = (1, 2)
print(x, y)
実行結果
1 2
従って、先程のプログラムは、下記のように記述することができます。先ほどのプログラムと下記のプログラムは 全く同じ処理 を行うプログラムなのでどちらを記述しても問題はありませんが、下記のプログラムの方が、3 行目の処理の 見た目がわかりやすくなる という利点があります。
for item in a.items():
(key, value) = item
print("key:", key, ", value:", value)
修正箇所
for item in a.items():
+ (key, value) = item
- print("key:", item[0], ", value:", item[1])
+ print("key:", key, ", value:", value)
実行結果
key: fruits , value: apple
key: price , value: 1000
key: color , value: red
さらに、上記のプログラムの 2 行目の処理は、下記のプログラムの 1 行目のように、for 文の 変数の部分にまとめる ことが出来ます。このような記述は実際に良く行われるので意味を覚えておくことをお勧めします。
for (key, value) in a.items():
print("key:", key, ", value:", value)
修正箇所
- for item in a.items():
+ for (key, value) in a.items():
- (key, value) = item
print("key:", key, ", value:", value)
実行結果
key: fruits , value: apple
key: price , value: 1000
key: color , value: red
tuple の ()
は 省略 することが出来ます。下記の 2 つのプログラムは、上記の 2 つのプログラムと 同じ処理 を行うプログラムです。実行結果は変わらないので省略します。
x, y = 1, 2
print(x, y)
修正箇所
- (x, y) = (1, 2)
+ x, y = 1, 2
print(x, y)
for key, value in a.items():
print("key:", key, ", value:", value)
修正箇所
- for (key, value) in a.items():
+ for key, value in a.items():
print("key:", key, ", value:", value)
ただし、以下の場合は、()
の省略はできません。
-
空の tuple の場合は
()
を省略できない -
()
を記述しないと、プログラムの 意味があいまいになる場合
曖昧になる例として、関数の実引数 に tuple のリテラルを記述 する場合が挙げられます。
例えば、print(1, 2)
と記述した時に、2 つの実引数 1
と 2
を記述したのか、1 つの tuple の実引数 (1, 2)
を記述したのかがあいまいになります。このような場合は、()
を省略することはできない ので、2 つの実引数 1
と 2
を記述 したことになります。
1 つの tuple のリテラル である (1, 2)
を実引数に記述 する場合は、print((1, 2))
のように記述する必要があります。このような記述は 慣れるまで分かりづらく感じられる かもしれませんが、実際に 良く使われる記述 なので覚えて下さい。
上記の 2 つを実行する下記のプログラムの実行結果から、上記について確認して下さい。
print(1, 2)
print((1, 2))
実行結果
1 2
(1, 2)
実際に、tuple の ()
は 良く省略される ので、以降は 省略してもわかりづらくならない場合 は 省略する こにします。
dict による名前空間の管理
dict は、識別子 である キー から、キーの値 を管理するオブジェクトを 対応づける というデータ構造です。キーには 文字列型 のデータを 指定 することが出来るので、dict を 名前空間を表すデータ としてそのまま 利用する ことが出来ます。
実際に、Python では 名前空間を dict で管理 しています。
グローバル名前空間とローカル名前空間を管理する dict
グローバル名前空間とローカル名前空間を管理する dict は、組み込み関数 globals
と locals
を使うことで取得することが出来ます。
下記のプログラムは、globals()
を使って グローバル名前空間を管理 する dict を表示 しています。Python のプログラムを実行すると、__name__
など、いくつかの グローバル変数が自動的に作られます。実行結果の最初に表示される {'__name__': '__main__'
などの部分はそのグローバル変数を表します。__name__
については 前回の記事 で説明したが、他の自動的に作られるグローバル変数の意味については必要になった時点で説明します。
JupyterLab で python のプログラムを実行 した場合は、JupyterLab が いくつかの グローバル変数を使った処理を行います、実行結果に表示される多くの名前は JupyterLab が作成したグローバル変数ですが、Python のプログラムを記述する際に、JupyterLab が利用したグローバル変数について 理解する必要はありません ので、それらの名前の意味については本記事では説明しません。
print(globals())
実行結果(かなり長いので全体を見るためには右にスクロールする必要があります)
{'__name__': '__main__', '__doc__': 'Automatically created module for IPython interactive environment', '__package__': None, '__loader__': None, '__spec__': None, '__builtin__': <module 'builtins' (built-in)>, '__builtins__': <module 'builtins' (built-in)>, '_ih': ['', 'a = { "fruits": "apple", "price": 1000 }\nprint(a["fruits"])\nprint(a["price"])\nprint(a)', 'print(a.fruits)', 'a["color"] = "red"\nprint(a["color"])\nprint(a)', 'for key in a:\n print(key)', 'print(a.keys())', 'for key in a:\n print(a[key])', 'print(a.values())', 'for value in a.values():\n print(value)', 'print(a.values()[0])', 'values = list(a.values())\nprint(values)\nprint(values[0])', 'print(a.items())', 'for item in a.items():\n print(item)', 'for item in a.items():\n print("key:", item[0], ", value:", item[1])', '(x, y) = (1, 2)\nprint(x, y)', 'for item in a.items():\n (key, value) = item\n print("key:", key, ", value:", value)', 'for (key, value) in a.items():\n print("key:", key, ", value:", value)', 'x, y = 1, 2\nprint(x, y)', 'for key, value in a.items():\n print("key:", key, ", value:", value)', 'print(1, 2)\nprint((1, 2))', 'print(globals())'], '_oh': {}, '_dh': [WindowsPath('c:/Users/ys/ai/marubatsu/020'), WindowsPath('c:/Users/ys/ai/marubatsu/020')], 'In': ['', 'a = { "fruits": "apple", "price": 1000 }\nprint(a["fruits"])\nprint(a["price"])\nprint(a)', 'print(a.fruits)', 'a["color"] = "red"\nprint(a["color"])\nprint(a)', 'for key in a:\n print(key)', 'print(a.keys())', 'for key in a:\n print(a[key])', 'print(a.values())', 'for value in a.values():\n print(value)', 'print(a.values()[0])', 'values = list(a.values())\nprint(values)\nprint(values[0])', 'print(a.items())', 'for item in a.items():\n print(item)', 'for item in a.items():\n print("key:", item[0], ", value:", item[1])', '(x, y) = (1, 2)\nprint(x, y)', 'for item in a.items():\n (key, value) = item\n print("key:", key, ", value:", value)', 'for (key, value) in a.items():\n print("key:", key, ", value:", value)', 'x, y = 1, 2\nprint(x, y)', 'for key, value in a.items():\n print("key:", key, ", value:", value)', 'print(1, 2)\nprint((1, 2))', 'print(globals())'], 'Out': {}, 'get_ipython': <bound method InteractiveShell.get_ipython of <ipykernel.zmqshell.ZMQInteractiveShell object at 0x0000024DA52D9D90>>, 'exit': <IPython.core.autocall.ZMQExitAutocall object at 0x0000024DA52E6250>, 'quit': <IPython.core.autocall.ZMQExitAutocall object at 0x0000024DA52E6250>, 'open': <function open at 0x0000024DA32B0040>, '_': '', '__': '', '___': '', '__vsc_ipynb_file__': 'c:\\Users\\ys\\ai\\marubatsu\\020\\marubatsu.ipynb', '_i': 'print(1, 2)\nprint((1, 2))', '_ii': 'for key, value in a.items():\n print("key:", key, ", value:", value)', '_iii': 'x, y = 1, 2\nprint(x, y)', '_i1': 'a = { "fruits": "apple", "price": 1000 }\nprint(a["fruits"])\nprint(a["price"])\nprint(a)', 'a': {'fruits': 'apple', 'price': 1000, 'color': 'red'}, '_i2': 'print(a.fruits)', '_i3': 'a["color"] = "red"\nprint(a["color"])\nprint(a)', '_i4': 'for key in a:\n print(key)', 'key': 'color', '_i5': 'print(a.keys())', '_i6': 'for key in a:\n print(a[key])', '_i7': 'print(a.values())', '_i8': 'for value in a.values():\n print(value)', 'value': 'red', '_i9': 'print(a.values()[0])', '_i10': 'values = list(a.values())\nprint(values)\nprint(values[0])', 'values': ['apple', 1000, 'red'], '_i11': 'print(a.items())', '_i12': 'for item in a.items():\n print(item)', 'item': ('color', 'red'), '_i13': 'for item in a.items():\n print("key:", item[0], ", value:", item[1])', '_i14': '(x, y) = (1, 2)\nprint(x, y)', 'x': 1, 'y': 2, '_i15': 'for item in a.items():\n (key, value) = item\n print("key:", key, ", value:", value)', '_i16': 'for (key, value) in a.items():\n print("key:", key, ", value:", value)', '_i17': 'x, y = 1, 2\nprint(x, y)', '_i18': 'for key, value in a.items():\n print("key:", key, ", value:", value)', '_i19': 'print(1, 2)\nprint((1, 2))', '_i20': 'print(globals())'}
下記のプログラムは、1 ~ 4 行目で a
という関数を定義して、6 行目で a
を呼び出しています。関数 a
では、ローカル変数 x
と y
に値を代入 した後で、組み込み関数 locals()
を呼び出すことで、ローカル名前空間 を管理する dict を表示 しています。
実行結果から、ローカル変数名をキー とし、その キーの値 として ローカル変数に代入された値 を持つ dict が表示されることを確認することが出来ます。
1 def a():
2 x = 1
3 y = 2
4 print(locals())
5
6 a()
行番号のないプログラム
def a():
x = 1
y = 2
print(locals())
a()
実行結果
{'x': 1, 'y': 2}
下記のリンク先から参照できる Python 公式ドキュメントでは、「ほとんどの名前空間は、現状では Python の辞書(注: dict のこと)として実装されていますが、(略)、将来は変更されるかもしれません」と記述されているので、一部の名前空間は dict 以外の方法で表現されているかもしれません。また、今後のバージョンでは、辞書以外の方法で表現されるかもしれないようです。
本記事が利用している Python のバージョン 3.11 では、今回の記事で説明する名前空間は dict で実装されているようです。
用語の再確認
クラスとインスタンスの仕組みを理解するために必要な、dict の説明と、名前空間と dict の関係について説明したので、次は、クラスとインスタンスの仕組みについて説明します。
前回の記事で説明したように、クラスとインスタンスはいずれも オブジェクトの一種 なので、正式にはクラスオブジェクト、インスタンスオブジェクトのように表記しますが、表記が長くなるので クラス、インスタンス のように 省略して表記される場合が多い ようです。本記事でもそのように省略して表記しますが、いずれもオブジェクトの一種であることを 忘れないで下さい。
クラスの仕組み
クラスの定義の実行時に行われる処理
Python では クラスの定義を実行 すると、以下のような処理が行われます。
- クラスのローカル名前空間 を表す 空の dict を管理する オブジェクトが作成 される
- クラスの ブロックの中 に記述された プログラムが実行 される
- 変数に値が代入 されたり、メソッドが定義 された場合は、それらの 名前とオブジェクトの対応づけ が、クラスのローカル名前空間に登録 される
- クラスの ブロックの処理が終了 した時点で、クラスを表すデータを管理する オブジェクトが作成 される。オブジェクトが管理するデータは、ローカル名前空間 を管理する オブジェクトの id である
- クラス名 と 同じ名前の変数 に クラスを表すデータが代入 される
上記の手順は、下記のリンク先の Python の公式ドキュメントで説明されています。
例えば、上記の手順 4 で行われる処理は「クラスオブジェクトは、基本的にはクラス定義で作成された名前空間の内容をくるむラッパ (wrapper) です」のように説明されています。
クラスの定義の実行 と、関数呼び出し は、「ローカル名前空間が作られる」、「ブロックの中 に記述された プログラムが実行 される」という 2 つの点では、同様の処理 が行われます。
ブロックの処理 を行う際の、クラスの定義と関数呼び出しの 相違点 は以下の通りです
- 関数呼び出し の場合は、仮引数 によって データを受け取る ことが出来る
- クラスの定義 の場合は、仮引数のようなものは存在しない
- 関数呼び出しの場合は、関数呼び出しが終了した時点で 関数のローカル名前空間は破棄 される
- クラスの定義の実行の場合は、上記の手順 4 のように、クラスのローカル名前空間は 破棄されず に、クラス が管理するデータによって 参照 される
具体例として、前回の記事で定義した Marubatsu
クラス を例に説明します。ただし、下記の Marubatsu
クラスの定義は、前回の記事の最後で追加した、イニシャライザである __init__
メソッドを削除 しています。その理由については後で説明します。
class Marubatsu:
def initialize_board(self):
self.board = [["."] * 3 for y in range(3)]
def place_mark(self, x, y, mark):
if self.board[x][y] == ".":
self.board[x][y] = mark
else:
print("(", x, ",", y, ") のマスにはマークが配置済です")
def display_board(self):
for y in range(3):
for x in range(3):
print(self.board[x][y], end="")
print()
上記の Marubatsu
クラスの定義を実行 すると、以下のような処理が行われます。
-
Marubatsu
クラスの ローカル名前空間 を表す 空の dict を管理する オブジェクトが作成 される(下図の ID が 351 のオブジェクト。なお、図はこの手順 1 ~ 5 を実行した後の状態です) - クラスの ブロックの処理が開始 される
- クラスのブロックの中に、3 つのメソッドが定義 されているので、それらのメソッドの定義を管理する 3 つのオブジェクトが作成 される(下図の右の 3 つのオブジェクト)
-
initialize_board
、place_mark
、display_board
という 3 つの名前 と上記の 3 つのオブジェクト の 対応づけ が クラスのローカル名前空間 を表すオブジェクトに 登録 される - クラスの ブロックの処理が終了 した時点で、クラスが作成 される(下図の ID が 852 のオブジェクト)。クラスが管理するデータ は、ローカル名前空間 を管理する オブジェクトの id である
-
クラスを表すデータ が
Marubatsu
という名前の変数に 代入 される(下図のグローバル名前空間への登録)
下図は、Marubatsu
クラスの定義を実行 した後の様子を図示したものです。ただし、図ではクラスの定義のブロックの中に記述したプログラムとは別に 自動的に作成 される 特殊属性に関するデータは省略 しています。真ん中の ID が 351 のオブジェクトが、名前空間 を表す dict を 管理するオブジェクト です。
クラスの定義と、関数の定義は、どちらも 〇〇の定義 のように記述されるので、同じような処理を行っていると 勘違いする 人がいるかもしれません。
実際には、クラスの定義の実行 で行われる処理に似ているのは、関数呼び出し で行われる処理です。関数の定義の実行 で行われる処理とは 似ていない 点に注意して下さい。
例えば、関数の定義を実行 した際に、関数の定義のブロックの中 に記述されている処理は 実行されません が、クラスの定義を実行 した場合は、クラスの定義のブロックの中 に記述されている処理は 実行されます。
もちろん、クラスの定義の実行と関数呼び出しは 異ななる処理 なので、似ていない部分 も 数多く あります。例えば、クラスの定義 は基本的には 一度だけ実行されます が、関数呼び出し は必要に応じて 何度でも実行される という点が異なります。
クラスの定義の実行で行われる処理の意味
クラスの定義の実行で行われる処理をまとめると以下のようになります。
- クラスのローカル名前空間を作成 する
- クラスのブロックの処理を実行し、そこで 定義された名前 をクラスのローカル名前空間に 登録 する
- クラスのローカル名前空間 を管理する クラスを作成 する
前回の記事で、オブジェクトが 管理するデータ のこと 属性(データが関数の場合は メソッド)と呼び、属性は「オブジェクト.属性名」と記述するという説明を行いました。これは、「属性名」や「メソッド名」という 名前 から、名前に代入された値を管理する オブジェクトを対応づける 処理が 必要になる ということを表します。実際に、Python のオブジェクトは、自身が管理する名前 と データ を 対応づける名前空間 をそれぞれ 個別に持っています。
クラスの定義で行われる処理は、クラスを作成 し、そのクラスが 管理 する属性やメソッドなどの 名前 から、名前に代入されたデータを管理するオブジェクトを 対応づける ための 名前空間を作成 するという処理です。
クラスのローカル名前空間は、関数のローカル名前空間と 同様の手順 で作成されます。ただし、関数のローカル名前空間 が関数呼び出しが終了した時点で 破棄される のに対して、クラスのローカル名前空間 は クラスの名前空間 として 引き続き利用される ため、クラスの定義の実行が終了しても 破棄されません。
クラスの属性名やメソッド名の名前解決
クラスの定義の実行によって作成された、Marubatsu
などの クラス に対して .名前
を記述 した場合の 名前解決 は以下の手順で行われます。手順の()の中は、Marubatsu.initialize_board()
を実行した場合の名前解決と下図の対応を表します。
- クラスが参照 する、名前空間 を表す dict を 管理するオブジェクトを探す(下図の②)
- dict の キー の中から 名前を探す
- 名前が見つかった 場合は、その キーの値 を管理するオブジェクトに 名前を対応づける(下図の③)
- 名前が 見つからなかった 場合は、Attribute Error というエラーが発生する
以前に説明した名前解決の手順と 異なり、名前が見つからなかった場合に グローバル名前空間 や ビルトイン名前空間 を 探しに行く ということは しない 点に 注意して下さい。
名前が半角の .
で区切られている場合は、先頭の名前 から 順番に名前解決 が行われます。例えば、Marubatsu.initialize_board
のような記述を行うと、上図の ① で グローバル名前空間 を使って Marubatsu
の名前解決 を行った後で、②、③ の順で クラスの名前空間 を使って initialize_board
の名前解決 が行われ、ID が 533 の initialize_board
の定義を管理するオブジェクトに対応づけられます。
厳密には、上記の手順 4 は正確ではなく、名前が見つからなかった場合に別の方法で名前解決を行う場合があります。具体的には、いくつかの特殊属性 は、名前空間を表す dict に 名前が登録されません。また、まだ説明していないクラスの継承や、__getattr__
などの特殊メソッドが定義されている場合などでは、別の方法で名前解決が行われます。
ただし、特殊属性以外の場合は、これまでに紹介してきたクラスの定義では発生しないので、基本的には、一部の特殊属性の名前を除いて、上記の手順で名前解決が行われる と考えて 問題はありません。一部の特殊属性の名前に関しては、何らかの特殊な方法で名前解決が行われていると思ってください。
別の方法で名前解決が行われる場合については、必要になった時点で紹介します。
名前解決でどの名前空間が使われるかの見分け方
名前解決を行う際に、以前の記事で説明した ビルトイン名前空間、グローバル名前空間、ローカル名前空間 が使われるのか、今回の記事で説明した オブジェクトの名前空間 が使われるかがわかりづらいと思っている方が多いかもしれませんが、実際には 簡単に見分ける ことが出来ます。
名前解決を行う名前空間は以下の方法で見分けることができる。
-
board
や、Marubatsu
のように、半角の.
で始まらない、先頭に記述された名前 は、以前の記事で説明した ビルトイン名前空間、グローバル名前空間、ローカル名前空間 を使って名前解決が行われる -
半角の
.
の後 に記述された 名前 は、.
の前 に記述された オブジェクトが管理する名前空間 を使って名前解決が行われる
.
の後 に記述された 名前 の名前解決が行われる際に、ビルトイン名前空間、グローバル名前空間、ローカル名前空間は 使われない 点に注意して下さい。
クラスの名前空間の確認
クラス の 名前空間 を管理する dict は、クラスの 特殊属性 __dict__
に自動的に 代入 されます。従って、下記のプログラムのように、print(Marubatsu.__dict__)
を記述することで、Marubatsu
のクラスの 名前空間を表示 することが出来ます。
print(Marubatsu.__dict__)
実行結果
{'__module__': '__main__', 'initialize_board': <function Marubatsu.initialize_board at 0x0000024DA5FCC0E0>, 'place_mark': <function Marubatsu.place_mark at 0x0000024DA5FCC900>, 'display_board': <function Marubatsu.display_board at 0x0000024DA5FCD120>, '__dict__': <attribute '__dict__' of 'Marubatsu' objects>, '__weakref__': <attribute '__weakref__' of 'Marubatsu' objects>, '__doc__': None}
クラス は 作成された時点 で、__dict__
以外 にも いくつかの特殊属性 が自動的に 作成 されます。そのため、Marubatsu.__dict__
には 多くの名前が登録 されています。dict を print
で表示 すると 横に並べて表示 が行われるため、上記の実行結果のように dict の全体像が分かりづらく なります。そこで、dict の 値をキーごとに改行して表示 することにします。
dict の 値をキーごとに改行 して表示するには、pprint5 というモジュールの中で定義されている pprint
6 という名前の関数を利用するのが便利です。pprint
は print
と同様に、実引数の値を表示する処理を行う関数ですが、その際に 表示が見やすくなるように整形 された表示を行ってくれます。
「その 17」の記事でも説明しましたが、python には、あらかじめいくつかのモジュールが用意されており、そのようなモジュールの事を 標準ライブラリ(または 組み込みモジュール)と呼びます。標準ライブラリは インストールしなくても利用 することが出来、pprint モジュールは標準ライブラリです。
python の標準ライブラリの詳細については、下記のリンク先を参照して下さい。
また、pprint モジュールの詳細については、下記のリンク先を参照して下さい。
下記のプログラムは 1 行目で、pprint
モジュールの中の pprint
関数をインポート し、3 行目で pprint
を使って Marubatsu
の __dict__
属性を表示 しています。print
で表示した場合と異なり、値がキーごとに改行 されて表示が行われます。
from pprint import pprint
pprint(Marubatsu.__dict__)
実行結果
mappingproxy({'__dict__': <attribute '__dict__' of 'Marubatsu' objects>,
'__doc__': None,
'__module__': '__main__',
'__weakref__': <attribute '__weakref__' of 'Marubatsu' objects>,
'display_board': <function Marubatsu.display_board at 0x0000024DA5FCD120>,
'initialize_board': <function Marubatsu.initialize_board at 0x0000024DA5FCC0E0>,
'place_mark': <function Marubatsu.place_mark at 0x0000024DA5FCC900>})
実行結果に表示される mappingproxy
は、値を変更できない 点以外は、dict と同じ性質 を持つデータ型で、pprint
で表示すると、その内容は ()
の中 に dict と同じリテラルで表示 されます。
上記から、__dict__
属性に代入された dict には、7 つのキーに対応する値が格納 されていることがわかります。そのうちの 下の 3 つ が Marubatsu
クラスで定義 された メソッドの名前 とその値を管理する 関数オブジェクト です。また、一番上に __dict__
属性とその値が格納されていることがわかります。
__dict__
属性は、クラスの属性やメソッドに対する処理を行うと 自動的にその値が更新されます。クラスの __dict__
属性のデータ型は、値を変更することができない mappingproxy
なので、クラスのメソッドの値を __dict__
属性を使って変更することはできません。
残りの 3 つの特殊属性の意味は以下の通りです。
-
__doc__
: docstring に記述した 文字列が代入 される特殊属性です。上記のMarubatsu
の定義では、docstring が 記述されていない ので None が代入 されています -
__module__
: クラスの定義が実行 された モジュールの名前 が代入される特殊属性です。上記の場合は、メインモジュール で実行したので__main__
が代入 されています -
__weakref__
: この特殊属性は今回の記事の内容と関係がないので説明は省略します
先程のノートで説明したように、全ての特殊属性の名前が __dict__
に登録されるわけではありません。例えば、__dict__
に登録されない特殊属性の一つに __class__
属性があります。この特殊属性の意味は必要になった時点で説明しますが、下記のプログラムのようにその値を表示することが出来ます。
print(Marubatsu.__class__)
実行結果
<class 'type'>
クラスのメソッド
前回の記事で説明 したように、クラスの メソッド を、インスタンスから呼び出した場合 は、メソッドの 仮引数 self
に、メソッドを呼び出した インスタンスが代入 されます。
一方、クラスのメソッドを クラスから呼び出した場合 は、通常の関数と同じ処理 が行われるため、メソッドの 仮引数 self
に、メソッドを呼び出したクラスが代入されることは ありません。
下記のプログラムは、initialize_board
メソッドを、クラス から 実引数を記述せず に呼び出していますが、実行結果からわかるように、仮引数 self
に対応する実引数が記述されていない という意味の エラーが発生 します。
Marubatsu.initialize_board()
実行結果
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
c:\Users\ys\ai\marubatsu\020\marubatsu.ipynb セル 25 line 1
----> 1 Marubatsu.initialize_board()
TypeError: Marubatsu.initialize_board() missing 1 required positional argument: 'self'
initialize_board
メソッドを クラスから呼び出す 場合は 通常の関数と同様 に、仮引数と同じ数 だけ 実引数を記述する必要 があります。従って、実引数にインスタンスを記述 して initialize_board
を呼び出すことで、インスタンスから initialize_board
メソッドを呼び出た場合 と 同じ処理 が行われます。
意味が分かりづらいと思いますので、具体例を挙げて説明します。下記のプログラムの 2 行目と 3 行目は、同一の処理 を行うプログラムです。ただし、2 行目のプログラムは、メソッドを クラスから呼び出した場合 の処理を 説明するため に記述したものです。実際には インスタンスに対する処理 を行う メソッドを呼び出す際 に、2 行目のような記述 を行うことは 通常はありません。
mb = Marubatsu()
Marubatsu.initialize_board(mb) # この行と次の行は全く同じ処理を行う文である
mb.initialize_board()
参考までに、このことは、下記のリンク先の python の本家のドキュメントに「 x.f()
という呼び出しは、MyClass.f(x)
と厳密に等価なものです」のように説明されています。
クラスのメソッド が 通常の関数と同じ性質を持つ ことは、先ほどの pprint(Marubatsu.__dict__)
の実行結果の display_board
のキーの値 が、関数オブジェクトを表す <function
で始まる ことから確認できます。後で説明しますが、インスタンスのメソッド を print
で 表示 すると、上記とは 異なる表示 が行われます。
インスタンスの仕組み
インスタンスの作成と名前空間
前回の記事では、インスタンス は、クラス という ひな形から作成 される オブジェクト であるという説明をしました。また、クラスからインスタンスを作成する際に、Marubatsu()
のように、関数呼び出しと同様の記述 を行うという説明をしました。
そのため、インスタンスを作成する際に、関数呼び出しと同様の方法で、インスタンスのひな形であるクラスの定義のブロックの内容が実行された結果、クラスと同じ属性とメソッドを持つインスタンスが作成されると 勘違いしている人がいるかもしれません。
(上記の説明は 間違った説明 なので、背景が赤色のノートに記述しています)
クラスと、インスタンスは、全く異なる方法で作成 されます。
実際には、インスタンスを作成 する際に、以下のような処理が行われます。手順の()の中は、mb = Marubatsu()
を実行した場合の処理と下図の対応を表します。
- インスタンスの名前空間 を表す 空の dict を管理する オブジェクトが作成 される(下図の ID が 669 のオブジェクト)
- インスタンスが作成 される(下図の ID が 193 のオブジェクト)。インスタンスが管理するデータ は、インスタンスの名前空間 を管理する オブジェクトの id と、クラスの id である
- クラスの定義に
__init__
メソッドが定義 されている場合は、インスタンスの作成時に記述された実引数が仮引数に代入されて__init__
メソッドが呼び出される。
下図は、mb = Marubatsu()
を実行した 直後の状況 を図示したものです。先ほどの Marubatsu
クラスの定義には、__init__
メソッドが定義されていないので、上記の手順 3 の処理は行われません。
図のように、インスタンス はひな形となった クラス と、インスタンスの名前空間 を管理する オブジェクトを参照 します。また、__init__
メソッドが定義されていない クラスから作成された インスタンスの名前空間 に 名前は 一つも 登録されません。
先程、Marubatsu
クラスからイニシャライザである __init__
メソッドを削除した理由は、インスタンスの作成時の処理の説明 をする際に、インスタンスの 名前空間 に 名前が登録されていない ということを説明したかったからです。
Marubatsu
クラスの __init__
メソッド を前回の記事の最後で記述したように 定義 すると、インスタンスを作成した際 に、インスタンスの board
属性 に初期化されたゲーム盤が 代入 されてしまうため、インスタンスの 名前空間 に board
という名前が 登録 されてしまいます。
このように、__init__
メソッド は、一般的にインスタンスを 作成した際 に、インスタンスが 管理するデータ (属性)の 初期設定 を行う処理を記述します。
インスタンスの __dict__
属性
インスタンス にも、クラスと同様に インスタンスの名前空間が代入 される __dict__
という特殊属性 があります。下記のプログラムは、1 行目で mb
に新しく作成した Marubatsu クラスのインスタンスを代入し、2 行目でその __dict__
属性を表示しています。空の dict を表す {}
が表示 されることから インスタンスの名前空間 に 名前が登録されていない ことがわかります。
mb = Marubatsu()
print(mb.__dict__)
実行結果
{}
インスタンスを作成する際に、クラスの定義が実行 されることは ありません。また、__init__
メソッドが定義されていないクラスから 作られた直後のインスタンス は、属性もメソッドも 持ちません。
クラスの定義は、関数の定義と同様に、特別な場合を除いて 一度だけ実行 されるものです。インスタンスの作成時に 関数呼び出しと同じ記述 を行うのは、クラスの __init__
メソッドを呼び出す という処理が行われるためであり、クラスの定義が実行されるわけでは ありません。
理由は不明ですが、クラスの __dict__
属性とインスタンスの __dict__
属性は若干性質が異なるようです。例えば、クラスの __dict__
属性には __dict__
などの特殊属性の一部が登録されますが、インスタンスの __dict__
属性には特殊属性は一つも登録されません。他にもいくつかの性質が異なりますが、プログラムを記述する際に、__dict__
属性を利用することはほとんどないので、その違いを 気にする必要はない でしょう。
インスタンスの名前解決
インスタンスの名前空間 に 名前 が一つも 登録されていない にも関わらず、インスタンスからクラスで定義されたメソッドを呼び出すことが出来る ことを不思議に思う人が多いかもしれません。
インスタンス に対して .名前
を記述 した場合、以下の手順で名前解決が行われます。手順の()の中は、mb.initialize_board()
を実行した場合の名前解決と下図の対応を表します。
-
インスタンスが参照 する、インスタンスの名前空間 を管理する オブジェクトを探す(下図の②)
- 名前空間を表す dict の キーの中 から 名前を探す
- 名前が見つかった場合 は、その キーの値 を管理するオブジェクトに 名前を対応 づける
- 名前が 見つからなかった場合 は、インスタンスが参照 する、クラスを探す(下図の③)
-
クラスが参照 する クラスの名前空間 を管理する オブジェクトを探す(下図の④)
- 名前空間を表す dict の キーの中 から 名前を探す
- 名前が見つかった場合 (下図の⑤)は、下記の 名前が見つかった場合の 手順を行う
- 名前が 見つからなかった場合 は、Attribute Error という エラーが発生 する
手順 3-2 で 名前が見つかった場合 は、下記の処理を行います
- キーの値を管理するオブジェクトが 関数オブジェクトでない 場合は、名前を そのオブジェクトに対応づける(この場合の処理の実例については次回の記事で説明します)
-
関数オブジェクトの場合 は、メソッドオブジェクトを作成 する(下図の⑥の ID が 193 のオブジェクト)
- メソッドオブジェクトが管理するデータ は、関数オブジェクトの id と、インスタンスの id である
- 名前を作成した メソッドオブジェクトに対応づける
上記の手順は一見すると複雑そうに見えるかもしれませんが、複雑なのは .名前
に メソッドの名前を記述 した場合の メソッドオブジェクトに関する処理だけ で、それ以外の手順 は、通常の ローカル名前空間、グローバル名前空間、ビルトイン名前空間 で行われる 名前解決の処理 と 大きな違いはありません。
上記の手順をまとめると以下のようになります。
- インスタンスの名前空間 から 名前を探す
- 見つからなければ、クラスの名前空間 から 名前を探す
- それでも見つからなければ エラーが発生 する
クラスの名前空間 で 名前が見つかった場合 は以下の処理を行います。
- 属性名 の場合は、その 属性の値を管理 するオブジェクトに 対応づける
- メソッド名 の場合は、メソッドオブジェクトを作成 し、そのメソッドオブジェクトに 対応づける
名前を見つけるまでの手順 は、(ビルドイン名前空間 に相当する名前空間が 存在しない ことを除けば)過去の記事で説明した、ローカル名前空間 と グローバル名前空間 で行われる 名前解決 と 同様の手順 で処理が行われます。
それぞれの名前空間は以下の表のように対応づけることができます。
通常の名前空間 | オブジェクトの名前空間 |
---|---|
グローバル名前空間 | クラスの名前空間 |
ローカル名前空間 | インスタンスの名前空間 |
メソッドオブジェクトが作られる理由
メソッドの名前 が クラスの名前空間 で見つかった際に メソッドオブジェクトが作成 される 理由 について説明します。先程の、クラスからメソッドを呼び出した場合 に 通常の関数呼び出しと同じ処理 が行われるという説明を思い出して下さい。
通常の関数 は、呼び出された際に 最初の仮引数 に関数を呼び出した インスタンスを代入 するという機能を 持ちません。従って、インスタンスの メソッドの名前 を クラスのメソッド に そのまま対応づけてしまう と、インスタンスからメソッドを呼び出した際に、仮引数 self にメソッドを呼び出した インスタンスが代入されない ことになってしまいます。
メソッドオブジェクト は関数と同じように 呼び出すことが出来るオブジェクト で、呼び出した際に、最初の仮引数 self
にメソッドを呼び出した インスタンスを代入 する機能をもちます。
メソッドオブジェクトに関する処理 は、インスタンスからメソッドを呼び出した際に、仮引数 self にメソッドを呼び出した インスタンスが代入されるようにする ために行われるものです。
メソッドオブジェクトの確認
インスタンス に メソッドの名前 を記述した際に、メソッドオブジェクトに対応づけられる ことは、print
を使って、下記のプログラムの 3 行目のように確認することが出来ます。
比較できるよう に、2 行目では クラスのメソッドを表示 しています。2 行目では 関数オブジェクトを表す <function
で始まる表示が行われますが、3 行目では 異なる表示 が行われます。
mb = Marubatsu()
print(Marubatsu.initialize_board)
print(mb.initialize_board)
実行結果
<function Marubatsu.initialize_board at 0x0000024DA5FCC0E0>
<bound method Marubatsu.initialize_board of <__main__.Marubatsu object at 0x0000024DA52E69D0>>
3 行目の print
で表示される内容の意味は、以下のようになります。なお、at の後ろに表示される数字は、オブジェクトの id です。
メインモジュール(__main__)で定義された Marubatsu
クラスの(of) initialize_board
に紐づけられた(bound)メソッド(method)オブジェクト。
インスタンスが管理するデータが独立したデータである理由
インスタンス.名前
を記述した場合は、その 名前 は、その インスタンスの名前空間 を使って 名前解決 が行われます。インスタンスの名前空間は、インスタンスごとに作られる ので、異なる インスタンスであれば、異なる名前空間 で 名前解決 が行われます。
これが、異なるインスタンス が管理するデータが 独立したデータ である理由です。
具体例を、図を使って説明します。
mb1 = Marubatsu()
mb2 = Marubatsu()
mb1.a = 1
mb2.a = 2
print(mb1.a)
print(mb2.a)
実行結果
1
2
下図は、上記のプログラムを実行した場合の状況を図示したものです。図からわかるように、mb1
と mb2
に代入されたインスタンスは、それぞれ ID が 669 と 014 のオブジェクトが管理する 異なる名前空間 を 参照 します。従って、mb1
と mb2
の a
という属性の 名前解決 を行う際に 異なる名前空間が使われる ため、それぞれのインスタンスが管理する a
という 同じ名前の属性 のデータは、図の ID が 234 と 345 のオブジェクトのように、独立したデータ になります。
メソッドをクラスで定義する理由
メソッド はインスタンスが 管理するデータに対する処理 を行うものです。それぞれのインスタンスが管理する データが異なっていても、一般的にインスタンスが管理する データに対する処理 は 共通 します。
例えば、Marubatsu
クラスから 複数のインスタンスを作成 し、それぞれのインスタンスで ゲーム盤にマークを配置 していった場合、それぞれのインスタンスが管理する board
属性の値は異なった値 になります。しかし、それぞれのインスタンスの board
属性がどのような値を取ったとしても、initialize_board
、place_mark
、display_board
メソッドは 同じ処理を行います。
つまり、同じクラスから作成 されたインスタンスの メソッドが行う処理 は、すべてのインスタンスで共通 するということです。
メソッドをインスタンスではなく、クラスで定義するのは、同じクラスから作成 されたインスタンスが、共通のメソッドを持つ ようにするためです。
インスタンス が管理する データは独立 しているが、インスタンスが管理する データを処理するメソッド はすべてのインスタンスで 共通する という性質は、今回の記事で説明した インスタンスの名前解決の仕組み によって実現されています。
このことは、関数をグローバル名前空間で定義 し、関数のブロックの中の ローカル名前空間 で、グローバル空間で定義された関数を利用 するという点に似ています。
グローバル名前空間 が クラスの名前空間、ローカル名前空間 が インスタンスの名前空間 に 対応する ことを思い出してください。
次回の記事について
長くなりましたので、今回の記事はここまでにします。次回の記事では、今回の記事で説明しなかった、クラスの属性の使い方などについて説明します。
本記事で入力したプログラム
以下のリンクから、本記事で入力して実行した JupyterLab のファイルを見ることができます。
今回の記事では、marubatsu.py
の変更はありません。
次回の記事
-
例えば、
([1, 2], 3)
のように、tuple の要素の中にミュータブルな list が存在する場合は、このデータはハッシュ可能ではありません ↩ -
追加した順番でキーが取り出されるようになったのは、Python の バージョン 3.7 からです ↩
-
subscript は、$ x_1 $ の 1 のような、数学の変数の右下に小さく表示する 下付き文字 のことで、同じ名前 の変数で 複数の数値を区別して扱う 際に使用します。python では、list や dict の右に記述する
[]
の事を表します ↩ -
pprint
の先頭の p は、「きれいな」という意味を表す pretty の頭文字です ↩ -
紛らわしいですが、
pprint
という 名前のモジュール の中に、同じ名前 のpprint
という 関数が定義 されています。import pprint
でモジュールをインポートした場合は、この関数はpprint.pprint
のように記述します ↩