またpythonの小ネタです。
概要
- クラスのプロパティに
setattr
してもプロパティのsetterは起動する - おまけ要素:
__dict__
に書き込んでもプロパティに書き込めない
落ち着いて考えてみれば「そりゃそうだろうな」って話ですがちょっと試します。
実験してみる
class PropClass:
"""プロパティvalueを持つクラス"""
def __init__(self) -> None:
"""プロパティの初期化"""
self._value = 'no changed.'
@property
def value(self):
""" getter プロパティをそのまま返す"""
return self._value
@value.setter
def value(self, v:str) -> None:
""" setter vを加工してsetする"""
self._value = f'"{v}"がsetterに渡された'
def __repr__(self) -> str:
return f'{self.value}'
if __name__ == '__main__':
# 普通に代入する
instance = PropClass()
instance.value = '普通に代入する'
print(instance)
# setattrで書き込む
instance = PropClass()
setattr(instance, 'value', 'setattrで書き込む')
print(instance)
もちろん出力はこうです。
"普通に代入する"がsetterに渡された
"setattrで書き込む"がsetterに渡された
そもそもpythonのドキュメントにはこう書いてあります。
setattr(x, 'foobar', 123) は x.foobar = 123 と等価です。
なので、素直に読めばinstance.value = '文字列'
とsetattr(instance, 'value', '文字列')
は等価です。しかし「プロパティの場合でもそう」とは書いていないので、「本当にそうなのか」と試してみました。結果はそのままドキュメントの通りでした。
実際に使うシーンはあまりないと思いますが、pythonの標準ライブラリの中でも任意に与えたオブジェクトにsetattr
してくるケースはあります。例えばargparse
なんかがやってきます。 1
その他には、使う機会はあまりないはずですがこんなケースも考えられます。
for k,v in somedict.items():
setattr(obj, k, v)
dictの中身を一方的にsetattr
されるコードなので何を書きこまれるか解ったものではありません。この動作に対してpropertyのsetterによって介入することができます。2
おまけの検証 propertyと__dict__
さて、ここまでのテストコードでsetattr
はpropertyのsetterと共存可能だと解りました。
しかし、pythonには他にもpropertyに侵攻できそうな方法があります。そのうちのひとつとして、instance.__dict__
からアクセスする方法を試しました。
instance = PropClass()
instance.__dict__['value'] = 'dictにプロパティ名を指定して書き込む'
print(instance)
出力はno chabged.
となり、プロパティのsetterも呼ばれません。しかし、instance.__dict__['value']
にはdictにプロパティ名を指定して書き込む
の値が設定されています。ここは詳しく調べると何故こうなるかわかるはず。なお
元々__dict__
には書き込むべきではないので、やらないのが無難でしょう。なお書き込み前にinstance.__dict__['value']
を呼び出すとKeyError
が返ります。プロパティは最初から__dict__
には含まれていません。