Pythonのクラス変数とインスタンス変数の違い
動機
Pythonの言語リファレンスとか他の人のコードみてたらクラス変数とインスタンス変数の違いがよくわからなくなったのでメモ。
というか、http://d.hatena.ne.jp/Ponsuke/20090128/1233115400にインスパイアされて書きました。
今まで
自分が最初に触ったオブジェクト指向言語はJavaで、そっちの概念を引きずったままPythonをやり始めたので当然クラス変数とインスタンス変数は別々に宣言するものなんだろという考えていた。
つまり
class Hoge(object): FOO = int()
とすればクラス変数FOOが宣言されたことになりインスタンス変数は宣言されてないものだと思ってた。
そしてPythonでインスタンス変数を使うには
class Hoge(object): def __init__(self): self.FOO = int()
として、クラス変数とインスタンス変数両方使うには上の2つを組み合わせ、
class Hoge(object): FOO = int() def __init__(self): self.FOO = int()
とやるべきという認識でいた。
しかしこれが間違っていてずっとそれを使っていたのでここに記しておこうと思った。
言語仕様
Pythonの言語リファレンスを見てみると
クラス定義内で定義された変数はクラス変数です; クラス変数は全てのインスタンス間で共有されます。
とされている。
つまり
class Hoge(object): FOO = int()
このコードはクラス変数で間違いない。
さらに、
となっていて、
class Hoge(object): def __init__(self): self.FOO = int()
これで作成しているはず。
クラス変数もインスタンス変数も “self.name” 表記でアクセスすることができます。この表記でアクセスする場合、インスタンス変数は同名のクラス変数を隠蔽します。
これはコードでいうと
class Hoge(object): FOO = int() def __init__(self): self.FOO = int() def get_foo(self): return self.FOO
になって、「selfのスコープから見えるFOOがインスタンス変数のFOOだからクラス変数のFOOにはアクセスできないよ」と言ってるんだろう。
実際どうなのよ?
先人の見解を参考にしつつ自分で書いたコードが以下。
いつものようにunittestで書いてるけどassertTrueにしてみた。
若干こっちの方が見やすい気がする。
本当はdoctestの方がassert文とかなくて見やすいからいいんだろうけど、エディタのシンタックスカラーが効かなくてコードが書きにくいのでunittestにしてます。
ちなみに実行環境は
Ubuntu10.10(32bit)
Python2.6.6
です。
#! python # coding:utf-8 """クラス変数とインスタンス変数 Pythonでのクラス変数とインスタンス変数を理解する為のコード片 """ import unittest class C(object): """クラス変数FOOとインスタンス変数FOOを持つクラス""" FOO = int() def get_foo(self): """インスタンス変数FOOの取得""" return self.FOO @classmethod def get_class_foo1(cls): """クラスメソッドを使っての取得""" return cls.FOO def get_class_foo2(self): """インスタンスメソッドだがクラス変数にアクセスしているもの""" return C.FOO class D(object): """クラス変数FOOは持たないがインスタンス変数FOOを持つクラス""" def __init__(self): """コンストラクタでインスタンス変数FOOを作る""" self.FOO = int() def get_foo(self): """インスタンス変数FOOの取得""" return self.FOO class TestClassVarAndInstanceVar(unittest.TestCase): def setUp(self): C.FOO = 10 # テスト毎にクラス変数を初期化 def test_modify_instance_var_only(self): c1 = C() # インスタンス変数へセットしているはず c1.FOO = 100 self.assertTrue(hasattr(c1, "FOO")) # インスタンスにFOOはある self.assertTrue(c1.FOO == 100) # これはインスタンス変数 self.assertTrue(c1.get_foo() == 100) # メソッド経由でインスタンス変数取得 def test_class_var_access(self): c1 = C() self.assertTrue(hasattr(C, "FOO")) # クラス変数にもFOOはある self.assertTrue(C.FOO == 10) # これはクラス変数 self.assertTrue(c1.get_class_foo1() == 10) # クラスメソッドでクラス変数取得その1 self.assertTrue(c1.get_class_foo2() == 10) # クラスメソッドでクラス変数取得その2 self.assertTrue(C.get_class_foo1() == 10) # クラスメソッドでクラス変数取得その3 self.assertRaises(TypeError, C.get_class_foo2) # インスタンスメソッドの呼び出しはできない(unbound) def test_modify_class_var_and_instnce_var(self): c1 = C() c2 = C() C.FOO = 20 self.assertTrue(c1.get_class_foo1() == 20) # クラス変数は20であり c1_cls_FOO = c1.get_class_foo1() c2_cls_FOO = c2.get_class_foo1() self.assertTrue(c1_cls_FOO == c2_cls_FOO) # クラス間で共有されている c1.FOO = 100 self.assertTrue(c1.get_foo() == 100) # インスタンス変数のFOOを変更しているはず self.assertTrue(c1.get_class_foo1() != 100) # 故にクラス変数は100ではなく self.assertTrue(c1.get_class_foo1() == 20) # 20であるはず def test_class_var_not_exists(self): d = D() d.FOO = 100 self.assertTrue(hasattr(d, "FOO")) # インスタンス変数FOOはあるが self.assertTrue(not hasattr(D, "FOO")) # クラス変数の変数FOOは存在しない if __name__ == '__main__': unittest.main()
実行結果はもちろんオールグリーンですよ。
.... ---------------------------------------------------------------------- Ran 4 tests in 0.000s OK
結論
ま、私の認識が間違っていましたってだけなんだけどね。