クラス変数とインスタンス変数の混同に注意
WEB で property 周りの記事を調べていたら、次のようなコードで、x や y をインスタンス変数だと解説している例を見つけた。
class C(object): x = 10 y = 20
確かに、
>>> c = C() >>> c.x # ... (1) 10 >>> c.x = 30 # ... (2) >>> c.x # ... (3) 30
となって一見正しいように見える。しかし、実際に起きているのは次の現象。
- (1) ではインスタンス変数 c.x が存在しないので、クラス変数 C.x の値 10 が読まれる。
- (2) で、インスタンス変数 c.x が作られ、そこに 30 が代入される。
- (3) ではインスタンス変数 c.x が存在するのでその値 30 が読まれる。クラス変数 C.x は隠される。
実際にこうなっていることは、id() 関数で変数のアドレスを確認すればはっきりする。
>>> c = C() >>> id(C.x) 134872388 >>> id(c.x) 134872388 >>> c.x = 30 >>> id(c.x) 134872148
c.x に値を代入する前は c.x と C.x は同じアドレスを示しているのに対し、代入後は c.x が別アドレスになっているのがわかる。
意図したようにインスタンス変数を使うためには、メソッドの中で self の属性として定義する必要がある。
class C(object): def __init__(self): self.x = 10 self.y = 20
property 周りの解説に書かれていたので、もしかすると、property の次の書式に引きずられたのかもしれない。
class C(object): ... x = property(get_x, set_x, del_x) z = 30 def y(self): ...
たしかに x と z は書式上、似ている。C のインスタンス c に対して c.x でインスタンス変数に (間接的に) アクセスできるので、z もインスタンス変数と思ってしまいがちかもしれない。しかし、z は単なる int 型の変数であるのに対し、x は property オブジェクトになるのが大きな違いである。def でメソッドを定義すると関数オブジェクトを作って C の名前空間の辞書に y という名前で登録するのと同じように、property オブジェクトを作って C の名前空間の辞書に x という名前で登録している。