LoginSignup
2
2

More than 1 year has passed since last update.

__setattr__よりpropertyのsetterを優先させたい場合

Posted at

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

参考
* https://stackoverflow.com/questions/15750522/class-properties-and-setattr

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2