2010-10-16

クラス変数とインスタンス変数 (Python編)

恥ずかしい告白をしなければならない。今日、「初めてのPython」の 「23 章 クラスのコーディング (基礎)」を読むまで、Python におけるクラス変数とインスタンス変数の記述の仕方を混同していた。というより、class ステートメントの内側に記述した(代入した)変数は、すべてインスタンス変数になると勘違いしていたのだ。

ここで、「クラス変数」とは、クラスのインスタンスにではなく、クラス自体が(つまりクラスオブジェクトが)持つ変数のことで、そのクラスのすべてのインスタンスオブジェクトで共有されるものを指す。一方で「インスタンス変数」は、それぞれのインスタンスオブジェクトに固有の変数のことだ。「初めてのPython」では、変数ではなく属性という言葉で表現しているが、この記事内では(わたし自身に馴染のある)変数という言葉で呼ぶことにする。

混同の原因は表記によるもの?

Python ではクラス変数は、

(クラス変数)
class Foo:
    value = 100

のように定義する。一方で、インスタンス変数は、以下のようにメソッド中で self に関連付けて定義する。

(インスタンス変数)
class Bar:
    def __init__(self, val):
        self.value = val

ややこしいのは、どちらも <インスタンスオブジェクト>.<変数名> としてアクセスできることが。つまり、以下のようなコードが書ける(FooBar は上述の定義のもの)。

(クラス変数とインスタンス変数へのアクセス)
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 経由で)代入した途端、インスタンス変数が作られるのだから、他のインスタンスに影響を及ぼすこともない。ただ、意図しないところでインスタンス変数が作られたり、そのおかげでクラス変数が無駄になっていたりするだけだ。

たとえ影響がないとしても直さないと……。みっともないからな。

参考文献

初めてのPython 第3版
Mark Lutz
オライリージャパン ( 2009-02-26 )
ISBN: 9784873113937
おすすめ度:アマゾンおすすめ度

0 件のコメント:

コメントを投稿