__setattr__
は、属性への代入をカスタマイズするためのスペシャルメソッドですが、property
のsetterよりも優先順位が高いです。したがって、下記のように、x
にsetterが定義されていても、x
への代入文では__setter__
が呼ばれており、メンバ変数_x
は作成されていません。
class Foo:
@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
def __setattr__(self, key, value):
pass
>>> f = Foo()
>>> f.x = 3
>>> f.x
AttributeError: 'Foo' object has no attribute '_x'
x
propertyのsetterを機能させたい場合、__setattr__
の中で、次のようにすることができます。
class Foo:
@property
def x(self):
return self._x
@x.setter
def x(self, value):
object.__setattr__(self, "_x", value)
def __setattr__(self, key, value):
if hasattr(type(self), key):
attr = getattr(type(self), key)
if isinstance(attr, property):
if hasattr(attr, 'fset'):
attr.fset(self, value)
else:
raise AttributeError("%s is read-only" % key)
else:
raise AttributeError("%s is not a property" % key)
このように__setattr__
を定義すると、下記のようにsetterが機能します。
>>> f = Foo()
>>> f.x = 3
>>> f.x
3
f.x = 3
の文で、__setattr__
では、
hasattr(type(self), key)
で、Foo
が属性x
を持つかどうか、
isinstance(attr, property)
で、Foo.x
がプロパティオブジェクトであるか、
hasattr(attr, 'fset')
で、そのプロパティオブジェクトがsetterをもつかがチェックされていて、
attr.fset(self, value)
で実際にsetterを呼んでいます。
注意しなければならないのは、x
のsetterの定義中で、単純にself._x = value
としてしまうと、カスタマイズした__setter__
が呼ばれてしまうので、デフォルトの代入動作を行うため、object
の__setattr__
を明示的に呼ぶようにしています。
参考