Python のクラス変数とインスタンス変数の危ない関係

Python でクラス変数を書き換えると生成済みのインスタンス変数にビックリな影響を与えることを知ったので、それについて書き留めておきます。使ったのは Python 3.5 です。

class Sandwich:
    ham = 0

メンバに変数 ham を持つ Sandwitch クラスを用意しました。このクラスのインスタンスを生成します。

>>> sw = Sandwich()
>>> sw.ham # インスタンス変数
0

当然、インスタンス変数は初期化された値が入ります。ではクラス変数を別の値に書き換えるとどうなると思いますか?

>>> Sandwich.ham = 1 # クラス変数に別の値を代入

そうすると…

>>> sw.ham # インスタンス変数
1

!?!?!? ٩(๑òωó๑)۶ wtf !?!?!?

予想外の結果ではないでしょうか? ぼくはまったく予想外でした。クラス変数の値の書き換えるとインスタンス変数の値も一緒に書き換わります。つまりインスタンス変数はクラス変数の参照なんですね。では、インスタンス変数に値を代入するとクラス変数はどうなるのか。

>>> sw.ham = 2
>>> Sandwich.ham
1

インスタンス変数を書き換えても、クラス変数は書き換わりません。ではもう一度、クラス変数を書き換えてみます。

>>> Sandwich.ham = 3
>>> sw.ham
2

書き換わりません。一度値を書き換えられたインスタンス変数は、クラス変数を書き換えても影響はありません。

要点をまとめるとこうです。

  • インスタンス生成時点ではインスタンス変数はクラス変数への参照。
  • このときクラス変数を書き換えるとインスタンス変数も書き換わる。
  • インスタンス変数は書き換えられるとクラス変数への参照が外れる。
  • それ以降はクラス変数の書き換えに影響を受けなくなる。

なんで Python がこんな仕様なのかよくわかりませんが、気をつけないと見つけにくいバグになりそうです。

One Comment

  1. Pythonのクラス変数参照の罠にかかっています。

    – インスタンス名.変数名 は、そのインスタンス変数が存在すればインスタンス変数の値を、なければクラス変数の値を返します。
    – インスタンス変数は参照だけでは生成されません。

    ですので、一つ目の
    >>> sw.ham # インスタンス変数
    は、クラス変数hamを参照しています。

    >>> sw.ham = 2
    によって、インスタンス変数が生成され、以降のsw.hamはインスタンス変数が参照されています。

    sw.ham=2の前後で、
    sw.__dict__
    を見ると、sw.ham=2でインスタンス変数が作成されているのがわかると思います。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

*