やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

Pythonでreadonlyにする方法。

丸パクリした。

成果物

GitHubPython.Const.201709132215

参考

丸パクリした。感謝。

前回まで

問題

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
  1. objectクラスの__setattr__メソッドを継承する(属性を設定するときに実行される)
  2. __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__を定義して属性を固定化したほうがいいかもしれない。
  • 破壊する手段はあるかもしれない。Pythonだし、きっと何かあるに違いない

所感

この手法を使えないか。まだ検討が必要そう。