LoginSignup
46
30

Pythonでクラス変数とインスタンス変数を取り違えてハマった

Last updated at Posted at 2020-06-07

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 = [];
    }
}
REPL で実行 (Node.js)
> 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.aundefined となり、append はエラーになります。

REPL で実行 (Node.js)
> 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 できます。

REPL で実行 (Node.js)
> t1.clear()
undefined
> t1.append(123)
undefined
> t1.a
[ 123 ]

Python と JavaScript を同時に扱う際には、インスタンス変数とクラス変数の仕様の違いに注意しましょう。

46
30
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
46
30