Python初学者です!
とある教材のコード課題を解いていてなかなか理解できなかったので、記事にすることにしました。
頑張って現時点で理解できているだけの用語と知識で言語化してみました。
もし、説明でおかしなところや理解がまだまだ浅い部分がありましたらつよつよエンジニアの皆さんよりご指摘いただけると幸いです><!!
(2021-01-22 @shiracamus さんよりご教授いただきましたので追記します!ありがとうございますm(_ _)m)
使用環境
macOS BigSur バージョン11.1
Google Colaboratory
課題
というわけで早速、その課題です。
Q. 次のクラスはバグを持っている。
複数インスタンスを行った場合addメソッドを呼び出すと、それぞれのオブジェクトにリストが共有されている。
これをデバッグし、正常に動作させよ。
class Sample:
li = []
def __init__(self, name):
self.name = name
def add(self, name):
self.li.append(name)
a = Sample('test1')
b = Sample('test2')
a.add('test1 a')
b.add('test2 b')
print(a.li)
# 出力結果
# ['test1 a', 'test2 b']
原因
これをどのようにすればいいのでしょうか。
まず、出力結果から考えると問題文の通り、['test1 a', 'test2 b']
と出力されているのが分かります。
本来は、どのような出力を期待していたのかというと、 コードを見る限り**print(a.li)
で['test1 a']
のみを出力したい**のだと推測できます。
このようなことが起きている原因がどこにあるのかというと、a.add('test1 a')
にもb.add('test2 b')
においても同じリストli = []
が使われているからだと考えられます。
ではなぜ、同じリストが使われてしまっているのでしょうか??
それはコンストラクタ外に変数が記述されている(=インスタンス変数ではなく、クラス変数になっている)からです。
これを理解するために必要な3つの知識をまとめます。
(私はこの3つのことをよく理解していませんでした・・・)
重要事項
①コンストラクタ
コンストラクタとはインスタンスが生成されるときに自動的に呼び出されるメソッドのことで、対象のクラスのインスタンスを初期化するために利用します。
※厳密には、「コンストラクタから呼び出される初期化メソッド」という方が正しいそうです。
コンストラクタは**「__init__」
で定義します。そしてクラスの中の関数の第一引数には「self」
**と指定します。
それぞれインスタンスを生成する際には、それぞれのデータを持たせたいわけですからこれによって初期化されるようになればいいわけです。
②インスタンス変数
(2021-01-22 @shiracamus さんよりご教授いただきましたので追記します!ありがとうございますm(_ _)m)
そしてこれを実装する際にもう一つ大事な概念、メソッド上で定義する変数のことをインスタンス変数と言います。
インスタンス変数は、それぞれのインスタンス(オブジェクト)ごとに独立しているという性質を持っています。
さらに、そのインスタンス変数には**self(インスタンス自身)**をつけるのがルールです。
基本の書き方は以下のようになります。
def __init__(self, 引数):
self.インスタンス変数 = 引数
なのでコンストラクタでインスタンス変数を定義するには、self.li = []
と書くのが良さそうです。
③クラス変数
クラス変数とははじめに書いていたようにclass直下に定義している変数のことです。
また、このクラス変数は全てのインスタンスで共有されるという性質を持っています。
だから、最初のコードではリストがa,bどちらのインスタンスにも共有されてしまったのです!(なるほど)
②③をまとめると、
1. インスタンスに変数を代入した場合は、インスタンスに変数が作られる(クラス変数とは別)
2. インスタンスに変数を代入しなければ、クラス変数を参照する
この辺りはよく混同しやすいそうで、以下の記事も参考にさせていただきました!
(@7shi さんありがとうございます)
[『Pythonでクラス変数とインスタンス変数を取り違えてハマった』][0]
[0]:https://qiita.com/7shi/items/d37493c58a8bb8d7beed
解決方法
ということで、以上のことを抑えたらコードを書いてみます。
class Sample:
def __init__(self, name):
self.name = name
self.li = []
def add(self, name):
self.li.append(name)
a = Sample('test1')
b = Sample('test2')
a.add('test1 a')
b.add('test2 b')
a.add('test3 a')
b.add('test4 b')
print(a.li)
print(b.li)
## 出力結果
## ['test1 a', 'test3 a']
## ['test2 b', 'test4 b']
出力結果もそれぞれのインスタンスに対してリストが生成されています
__init__
にてインスタンス変数を作ると、それぞれのインスタンス(aとb)毎に初期化されたliという空のリストが渡されているからですね!
まだまだなれませんが数をこなして慣れていきますっ!
では、次回もよろしくお願いします!