丸パクリした。
成果物
参考
丸パクリした。感謝。
前回まで
問題
Pythonでreadonlyな定数を作りたい。
解法
1. setattrで再代入禁止する
const.py
class _const: class ConstError(TypeError): pass def __setattr__(self,name,value): if name in self.__dict__.keys(): raise self.ConstError('readonly。再代入禁止。') self.__dict__[name]=value
- objectクラスの
__setattr__
メソッドを継承する(属性を設定するときに実行される) __setattr__
で属性名が既存ならエラーにする
_const
クラスのインスタンス属性に2度目の代入をしようとすると例外発生する。
inst = _const() inst.attr = 'a' inst.attr = 'a' # ConstError
以下2つの問題が残るが、以降で解決する。
- インスタンスを生成せねばならないのが面倒
- 全体で1つだけにしたい
2. moduleにclassインスタンスを代入する
const.py
class _const: ... import sys sys.modules[__name__]=_const()
sys.modules[__name__]=_const()
で、const.py
モジュールに_const
クラスのインスタンスを代入している。
0.py
import const print(const, type(const)) #<const._const object at 0xb712932c> <class 'const._const'>
$ python 0.py
で実行すると、上記の通り、モジュールにclassインスタンスが代入される。
もしconstがモジュールなら、以下のように表示されていたはずである。
<module 'const' from '/.../const.py'> <class 'module'>
sys.modules
sys.modules
の操作はPython文書によると、真っ当ではないらしい。でも今回のようなトリック操作ができる。
既に読み込まれているモジュールとモジュール名がマップされている辞書です。これを使用してモジュールの強制再読み込みやその他のトリック操作が行えます。ただし、この辞書の置き換えは想定された操作ではなく、必要不可欠なアイテムを削除することで Python が異常終了してしまうかもしれません。
3. classインスタンスの属性を動的生成する
import const const.attr = 'value' const.attr = 'value'
- 2の通り、constはモジュールではなく、
const._const
クラスのインスタンスである - 1の通り、2度めの代入で例外が発生する
これにてreadonlyな定数の実装が可能となる。間違って既存の名前で代入しようとするとエラーになる。
考えたこと
安心して使うために。
- 新規生成(定義)は1つのファイル内でやらないと名前重複が探しづらくなる。別々のファイルで定義すると、どのファイルで定義しているか見つけられなくなる。
- 定数はすべてconst.pyの中で定義したほうがいいかもしれない
__slots__
を定義して属性を固定化したほうがいいかもしれない。
- 定数はすべてconst.pyの中で定義したほうがいいかもしれない
- 破壊する手段はあるかもしれない。Pythonだし、きっと何かあるに違いない
所感
この手法を使えないか。まだ検討が必要そう。