Python でクラス変数をインスタンス変数と取り違えたため、思ったように動かなくてハマりました。参考までにメモしておきます。
概要
よくあるミスのようで、同じ話題を扱った記事があります。
ポイントはこのコメントに集約されています。
pythonの挙動は、self.odds を参照するとき、まずインスタンス変数を参照して、なければクラス変数を参照します。
別の記事でも注意喚起されています。
クラス変数にアクセスする場合は、特別な理由がない限り「インスタンス.クラス変数」や「self.クラス変数」のようにアクセスすることは避けるべきです。Python ではインスタンス変数をインスタンスオブジェクトから生成することができ、意図せずクラス変数をインスタンス変数で隠蔽してしまうことがあります。
これについて簡単な例で確認します。
落とし穴
クラス変数に self
経由でアクセスできてしまうので、インスタンス変数を定義したものだと勘違いしてしまいました。
これは再現するコードの全体で、省略はしていません。
class Test:
a = [] # この定義が問題
def append(self, value):
self.a.append(value)
def clear(self):
self.a = []
t1 = Test()
t1.append(123)
print(t1.a)
t1.clear()
print(t1.a)
[123]
[]
これを見て、値がクリアできていると思い込みました。実際には、クラス変数が同名のインスタンス変数で覆い隠されているだけです。
他のインスタンスを作ると、123
を追加した状態のクラス変数が見えます。
t2 = Test()
print(t2.a)
[123]
実際にハマったのは何度も clear
を呼ぶようなプログラムでした。挙動がおかしいことには気付いたものの、インスタンス生成直後の状態には注意が向かずに、原因究明が遅れました。
対策
クラスの直下での定義はクラス変数になります。インスタンス変数はコンストラクタで初期化しましょう。
class Test:
def __init__(self):
self.a = []
JavaScript
JavaScript ではクラスの直下で定義するとインスタンス変数になります。Python と同時に扱っていると混乱しやすいので注意が必要です。
私が Python でインスタンス変数を書いたつもりになっていたのも、言語仕様を取り違えたことによる混乱が原因です。
class Test {
a = []; // インスタンス変数
append(value) {
this.a.push(value);
}
clear() {
this.a = [];
}
}
> t1 = new Test()
Test { a: [] }
> t1.append(123)
undefined
> t1.a
[ 123 ]
> t1.clear()
undefined
> t1.a
[]
> t2 = new Test()
Test { a: [] }
> t2.a
[]
JavaScript ではクラス変数には static
を付けます。
class Test {
static a = []; // クラス変数
append(value) {
this.a.push(value);
}
clear() {
this.a = [];
}
}
クラス変数には this
経由でアクセスできないため this.a
は undefined
となり、append
はエラーになります。
> t1 = new Test()
Test {}
> t1.append(123)
Uncaught TypeError: Cannot read properties of undefined (reading 'push')
at Test.append (REPL11:5:16)
> t1.a
undefined
> Test.a
[]
インスタンス変数は動的に定義できるので、先に clear
を呼べば append
できます。
> t1.clear()
undefined
> t1.append(123)
undefined
> t1.a
[ 123 ]
Python と JavaScript を同時に扱う際には、インスタンス変数とクラス変数の仕様の違いに注意しましょう。