python:クラス生成で変数を初期化する場合にハマったこと

NO IMAGE

もともとC#で開発をしていた私が、Pythonで組み始めたところ、クラス生成の挙動が異なりハマってしまった。

配列は生成時に初期化されない?

試しにこんなコードを書いてみた。

class a:
    data = 0
    ar = []

    def addvalue(self):
        self.data += 1
        self.ar.append(self.data)
        return (self.data)


for i in range(0, 3):
    c = a()
    c.addvalue()
    print(str(c.data) + " : " + str(c.ar))

クラスaの定義で、dataとarを初期化している。つまりこれでc=a()とクラスを生成するごとに、dataとarは初期化されるはずである。

このサンプルではクラスaを生成し、addvalue(dataに1を足し、arに追加する)を呼び出し、クラスのdataとarを出力する動作を3回繰り返す。

期待していた出力はこうだ。

1 : [1]
1 : [1]
1 : [1]

ところが結果はこうだった。

1 : [1]
1 : [1, 1]
1 : [1, 1, 1]

クラスを生成するごとに、dataは初期化されているが、arは初期化されていない。これにより、2回目の生成時に、1回目に生成したクラスのarが引き継がれてしまっているのだ。

いや、引き継がれているのか、arがすべて同じ配列を示しているのか?

そこで次のようにコードを変えて再度実行してみる。

car=[]

for i in range(0, 3):
    c = a()
    c.addvalue()
    car.append(c)

for c in car:
    print(str(c.data) + " : " + str(c.ar))

結果はこうである。

1 : [1, 1, 1]
1 : [1, 1, 1]
1 : [1, 1, 1]

どうやらすべてのクラスで、arプロパティが同じ配列を指してしまっているようだ。

リストやクラスは__init__で初期化が必要?

そこで、aクラスに__init__コンストラクタを追加し、arを初期化してみた。

class a:
    data = 0
    ar = []

    def __init__(self): #コンストラクタ
        self.ar=[]

    def addvalue(self):
        self.data += 1
        self.ar.append(self.data)
        return (self.data)


car = []

for i in range(0, 3):
    c = a()
    c.addvalue()
    car.append(c)

for c in car:
    print(str(c.data) + " : " + str(c.ar))

結果はこうなった。期待通りである。

1 : [1]
1 : [1]
1 : [1]

コンストラクタではself指定忘れに注意

ちなみにこの前にもう一つ失敗があった。前のコードのコンストラクタで、ar=[]としたところ、結果が変わらなかった。

なんのことはない、self.ar=[]としなかったため、使われないローカル変数を指定するのみで、クラスのarを初期化できていなかった。

当たり前といえば当たり前なのだが、これ自体は文法的になんの問題もないため、すんなりと実行されてしまう。つまりバグとして見つけにくいのだ。

変数を宣言せずに使える言語は手軽ではあるが、その分バグの発生リスクは高まる。

クラスの初期化については重々注意しておきたい。