はじめに
PythonでClassのメンバ変数(アトリビュート)の値が不意に書き換わっていて困ったときに調べた内容のメモです.(既存のコードでClassのメンバ変数が外から直接書き換えられてるっぽい!?みたいなしんどい状況)
プロパティに変更する
ぱっと思いつくのはプロパティを用意することですね.あまりPythonで書いたことがなかったのと,プロパティの書き方が何種類かあっていつも調べてしまうので,簡単にまとめておきます.
ここではデコレータを使った書き方を載せておきます.
class Spam():
def __init__(self, value):
self._value = value
@property
def value(self):
print(f'Get Value: {self._value}')
return self._value
@value.setter
def value(self, value):
self._value = value
print(f'Set Value: {self._value}')
def main():
spam = Spam(42)
print(spam.value)
spam.value = 23
print(spam.value)
if __name__ == "__main__":
main()
$ python spam.py
Get Value: 42
42
Set Value: 23
Get Value: 23
23
setter,getterメソッドを用意してproperty
関数で登録する書き方もありますが,デコレータを使った方法のほうがシンプルかと思います.また読み取り専用のプロパティにしておきたい場合は,setterを用意する必要はありません.
外部からClassのメンバ変数を直接書き換えるようなコードになっていても,同名のプロパティを用意すれば呼び出し側のコードを変更せずに今回のようなログを追加することができそうです.
__setattr__をオーバーライド
ざっとメンバ変数の更新を確認したいだけなら,__setattr__
という特殊メソッドを使う方法もあります.
spam.value = 23
みたいなコードを書くと,内部で__setattr__が呼ばれているため,このメソッドをオーバーライドすることでロギング処理などを差し込めます.
class Spam():
def __init__(self, value):
self.value = value
def __setattr__(self, name, value):
print(f'Set {name}: {value}')
object.__setattr__(self, name, value)
def main():
spam = Spam(42)
print(spam.value)
spam.value = 23
print(spam.value)
if __name__ == "__main__":
main()
$ python spam.py
Set value: 42
42
Set value: 23
23
注意点は__setattr__の中で基底クラス(明示的に継承していなければobject
)の__setattr__を呼び出さないと値がセットされなくなります.
確認をしたいメンバ変数が複数ある場合でも__setattr__をひとつ実装するだけで済みますし,逆に特定のメンバ変数だけ確認を取りたい場合は引数で渡ってくるname
を見てロギング処理を分岐させるとよいです.
おわりに
普段の単純なコードではあまり__setattr__なんかを触る機会は少ないかもですが,知っておくと何かのときに便利ですね.