恥ずかしい告白をしなければならない。今日、「初めてのPython」の 「23 章 クラスのコーディング (基礎)」を読むまで、Python におけるクラス変数とインスタンス変数の記述の仕方を混同していた。というより、class
ステートメントの内側に記述した(代入した)変数は、すべてインスタンス変数になると勘違いしていたのだ。
ここで、「クラス変数」とは、クラスのインスタンスにではなく、クラス自体が(つまりクラスオブジェクトが)持つ変数のことで、そのクラスのすべてのインスタンスオブジェクトで共有されるものを指す。一方で「インスタンス変数」は、それぞれのインスタンスオブジェクトに固有の変数のことだ。「初めてのPython」では、変数ではなく属性という言葉で表現しているが、この記事内では(わたし自身に馴染のある)変数という言葉で呼ぶことにする。
混同の原因は表記によるもの?
Python ではクラス変数は、
(クラス変数) class Foo: value = 100
のように定義する。一方で、インスタンス変数は、以下のようにメソッド中で self
に関連付けて定義する。
(インスタンス変数) class Bar: def __init__(self, val): self.value = val
ややこしいのは、どちらも <インスタンスオブジェクト>.<変数名> としてアクセスできることが。つまり、以下のようなコードが書ける(Foo
と Bar
は上述の定義のもの)。
(クラス変数とインスタンス変数へのアクセス) foo = Foo() bar = Bar(300) print foo.value, bar.value
これを実行すると、
100 300
と表示される。本来、Foo
クラスのクラス変数 value
へのアクセスは Foo.value
と表記すべきだが、Foo
クラスのインスタンスを通して foo.value
でも参照できるのだ。さらにややこしいことに、foo.value
に対して代入すると、今度はクラス変数の value
への代入ではなく、新しく value
という名のインスタンス変数が定義される。つまり、その時点で foo
オブジェクトはクラス変数としての value
とインスタンス変数としての value
の両方を持つオブジェクトに変わる。ただ、代入でインスタンス変数が定義されるのは foo
オブジェクトに対してだけなので、新しく Foo
クラスのインスタンスを作ったなら、そのオブジェクトはクラス変数の value
しか持っていない。
ああ、ややこしい。
サンプルプログラム
このことを確かめるためのサンプルプログラムを書いてみた。Foo
クラスは上述のものと同じ。一方、Bar
は同名のクラス変数とインスタンス変数を持ったものにしてある。
(variables.py) 1: #!/usr/bin/python2.5 2: 3: class Foo: 4: value = 100 5: 6: class Bar: 7: value = 200 8: def __init__(self, val): 9: self.value = val 10: 11: print "Foo:" 12: foo1 = Foo() 13: foo2 = Foo() 14: print "foo1.value = %d, Foo.value = %d" % (foo1.value, Foo.value) 15: print "foo2.value = %d, Foo.value = %d" % (foo2.value, Foo.value) 16: 17: print "---- set 200 to foo1.value" 18: foo1.value = 200 19: print "foo1.value = %d, Foo.value = %d" % (foo1.value, Foo.value) 20: print "foo2.value = %d, Foo.value = %d" % (foo2.value, Foo.value) 21: 22: print "---- set 50 to Foo.value" 23: Foo.value = 50 24: print "foo1.value = %d, Foo.value = %d" % (foo1.value, Foo.value) 25: print "foo2.value = %d, Foo.value = %d" % (foo2.value, Foo.value) 26: 27: print "Bar:" 28: bar1 = Bar(300) 29: bar2 = Bar(400) 30: print "bar1.value = %d, Bar.value = %d" % (bar1.value, Bar.value) 31: print "bar2.value = %d, Bar.value = %d" % (bar2.value, Bar.value) 32: 33: print "---- set 500 to bar1.value" 34: bar1.value = 500 35: print "bar1.value = %d, Bar.value = %d" % (bar1.value, Bar.value) 36: print "bar2.value = %d, Bar.value = %d" % (bar2.value, Bar.value) 37: 38: print "---- set 600 to Bar.value" 39: Bar.value = 600 40: print "bar1.value = %d, Bar.value = %d" % (bar1.value, Bar.value) 41: print "bar2.value = %d, Bar.value = %d" % (bar2.value, Bar.value)
これの実行結果は以下のようになる
[imac] mnbi% python2.5 variables.py Foo: foo1.value = 100, Foo.value = 100 foo2.value = 100, Foo.value = 100 ---- set 200 to foo1.value foo1.value = 200, Foo.value = 100 foo2.value = 100, Foo.value = 100 ---- set 50 to Foo.value foo1.value = 200, Foo.value = 50 foo2.value = 50, Foo.value = 50 Bar: bar1.value = 300, Bar.value = 200 bar2.value = 400, Bar.value = 200 ---- set 500 to bar1.value bar1.value = 500, Bar.value = 200 bar2.value = 400, Bar.value = 200 ---- set 600 to Bar.value bar1.value = 500, Bar.value = 600 bar2.value = 400, Bar.value = 600
14 〜 15 行目の出力で、Foo
のクラス変数をインスタンスオブジェクト経由で参照できることがわかる。さらに 18 〜 20 行目では、<インスタンスオブジェクト>.<変数名> という表記に対する代入がインスタンス変数を(そのオブジェクトに対してのみ)定義することがわかる。また、23 〜 25 行目では、明示的にクラス変数を指定した代入が可能なこともわかる。
Bar
を使った例では、クラス変数とインスタンス変数が同名の場合、<インスタンスオブジェクト>.<変数名> という表記ではインスタンス変数の方が優先されて参照されることがわかる。
これまでに書いたコードへの影響
この(恥ずかしい)混同のため、Blogger Glass のコードではクラス変数とインスタンス変数の区別が付いていない。よくもまあ動いているものだ。
実際のところは、参照だけなら(初期化後に一切代入していないなら)クラス変数であれ、インスタンス変数であれ、複数のインスタンスオブジェクトから参照されたとしても問題にならない。また、メソッドの中で(self
経由で)代入した途端、インスタンス変数が作られるのだから、他のインスタンスに影響を及ぼすこともない。ただ、意図しないところでインスタンス変数が作られたり、そのおかげでクラス変数が無駄になっていたりするだけだ。
たとえ影響がないとしても直さないと……。みっともないからな。
0 件のコメント:
コメントを投稿