前置き
次のようなコードを書いて、予想しない挙動に困っている人をしばしば見る。
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に対応させない』ことの方が大事な気もしてくる。