Python
コーディング規約
python3

Pythonのクラス変数を安全に書き換える

前置き

次のようなコードを書いて、予想しない挙動に困っている人をしばしば見る。

class MyClass:
    cls_var_int = 42
    cls_var_str = 'spam'

    def set_cls_vars(self, new_val_int, new_val_str):
        self.cls_var_int = new_val_int
        self.cls_var_str = new_val_str


inst1 = MyClass()
inst2 = MyClass()

inst1.set_cls_vars(6, 'ham')
assert inst1.cls_var_int is inst2.cls_var_int  # AssertionError! なんで!?

クラス変数としてのcls_var_intと、
インスタンス変数としてのそれが共存してしまっているのが原因だ。

>>> inst1.cls_var_int
6
>>> MyClass.cls_var_int
42

このような凡ミスを避ける一つの方法として、
常に「クラス名.属性名」でのアクセスを徹底する、というものがある。

class MyClass:
    cls_var_int = 42
    cls_var_str = 'spam'

    def set_cls_vars(self, new_val_int, new_val_str):
        MyClass.cls_var_int = new_val_int
        MyClass.cls_var_str = new_val_str

しかしこの場合、クラス名の変更に際して被影響範囲が広くなってしまう。

本題

この例に限ってはクラスメソッドを利用すれば解決するのだが、
都合上どうしてもインスタンスメソッド内でクラス変数を書き換えたい場合もある。

そのようなとき、個人的には次のように書いてしまうことが多い。

class MyClass:
    cls_var_int = 42
    cls_var_str = 'spam'

    def set_cls_vars(self, new_val_int, new_val_str):
        cls = type(self)

        cls.cls_var_int = new_val_int
        cls.cls_var_str = new_val_str


inst1 = MyClass()
inst2 = MyClass()

inst1.set_cls_vars(6, 'ham')
assert inst1.cls_var_int is inst2.cls_var_int  # AssertionErrorは起きない

type(self)の代わりにself.__class__を使うと、旧スタイルクラスにも対応できる。
ただ、逆に『2.7に対応させない』ことの方が大事な気もしてくる。

同様のポスト

Qiita - クラス変数にはどうアクセスすべき?