__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__を明示的に呼ぶようにしています。
参考