pythonで各種の設定データを外部化するために、configparserを使う機会が多いので、
どうにか、いい方法がないかなと考えたコードを記載します。
- 標準ライブラリのconfigparser
- デザインパターンのSingleton
- 組込関数のsetattrとproperty
を使用したクラスになります。
最後にコードを載せていますので、解説不要な方は最後に飛んでください。
configparser
iniファイル扱うための標準のクラスです。
pythonでiniファイルを取り扱う場合は、これ一択ぐらいで使用しています。
iniファイルの構成例
iniファイルの中にはセクションという1つのグループの中にキーと値のセットが1つ以上あります。
今回は下記のiniファイルを前提に説明します。
test1.ini
[hoge]
a = 0
b = 1
[fuga]
a = 10
b = 11
c = 12
test2.ini
[wkwk]
a = 0
Singleton
GoFのデザインパターンの生成に関するパターンのひとつである。Singltonを使用します。
Singletonを使用するメリット
Singletonを使用すると判断した理由として、
iniファイルはリソース
ということが、大きな決め手です。
- iniファイルの情報をもつインスタンスは1つの方が好ましい
- iniファイルの情報をもつインスタンスが複数だと、メモリの無駄遣いで冗長
Singletonのコード
Singleton用のテンプレート
class Singleton:
_instance = None
_init_lock = False
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, test):
if not self._init_lock:
self.initialize(test)
def initialize(self, test):
self._init_lock = True
self.test = test
@classmethod
def get_instance(cls):
return cls._instance
@classmethod
def clear_init_lock(cls):
cls._init_lock = False
pythonでのSingletonの実装は色んな書き方があります。
個人的には、上記のコードが1番使いやすく分かり易いかなと思っています。
コード解説
このコードについては、別途解説しようかなと思うので、現在は省略させていただきます。
setattrとproperty
pythonの組込関数であるsetattrとpropertyを使用します。
setattrとpropertyを使用するメリット
iniファイルは実装規模によって、キーと値の数が多かったり、少なかったりします。
多い場合など、そのキーに対応する属性を追加するのは、大変手間です。
その手間を省き、属性追加を自動で行うために、setattrを使用します。
また、propertyを使うことで、privateな変数のようにふるまえるようにします。
setattrとpropertyのコード
setattrとproperty用のコード
def add_property(cls, name, value=None, read=True, write=True):
field_name = f'_{cls.__class__.__name__}__{name}'
setattr(cls, field_name, value)
fget = (lambda cls: getattr(cls, field_name, value)) if read else None
fset = (lambda _, value: setattr(cls, field_name, value)) if write else None
setattr(cls.__class__, name, property(fget=fget, fset=fset))
今回は属性を追加するためにpropertyを使用します。
fsetとfgetには同じ組込関数のgetattrとsetattrを使用しています。
コード解説
add_propertyに与えるオプション引数の設定については下記のとおりです。
- readがTrueなら読み取り可、Falseなら読み取り不可
- writeがTrueなら書き込み可、Falseなら書き込み不可
- valueについては設定されないとNoneオブジェクトがデフォルト値として設定されます。
最強のコード
これまでで説明した、configparser、Singleton、proptery
を用いて、最強のコードを書いていきます。
ConfigParser特化クラス
import configparser
def add_property(cls, name, value=None, read=True, write=True):
field_name = f'_{cls.__class__.__name__}__{name}'
setattr(cls, field_name, value)
fget = (lambda cls: getattr(cls, field_name, value)) if read else None
fset = (lambda _, value: setattr(cls, field_name, value)) if write else None
setattr(cls.__class__, name, property(fget=fget, fset=fset))
class ConfigParserBase(object):
_instance = None
_init_lock = False
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, config_path):
if not self._init_lock:
self.initialize(config_path)
@classmethod
def get_instance(cls):
return cls._instance
@classmethod
def clear_init_lock(cls):
cls._init_lock = False
def initialize(self, config_path, encoding='utf-8'):
self._init_lock = True
self._config_path = config_path
self._config_file = configparser.ConfigParser()
self._config_file.optionxform = str
self._config_file.read(config_path, encoding=encoding)
for section_name in self._config_file.sections():
section = self._config_file[section_name]
print(type(section_name, (object,), dict())())
add_property(self, section_name, value=type(section_name, (object,), dict())())
for item, value in section.items():
add_property(getattr(self, section_name), item, value=value, write=False)
使い方
僕が考えた最強のConfigParser特化クラスの使い方は下記となります。
class Test1(ConfigParserBase):
def __init__(self, config_path):
super().__init__(config_path)
class Test2(ConfigParserBase):
def __init__(self, config_path):
super().__init__(config_path)
if __name__ == '__main__':
test1 = Test1('test1.ini')
test2 = Test2('test2.ini')
print('---------------------')
print(test1.hoge.a) # 0
print(test1.hoge.b) # 1
print(test1.fuga.a) # 10
print(test1.fuga.b) # 11
print(test1.fuga.c) # 12
print(test2.wkwk.a) # wkwk
print('---------------------')
get_instance = Test1.get_instance()
print(get_instance.hoge.a) # 0
print(get_instance.hoge.b) # 1
print(get_instance.fuga.a) # 10
print(get_instance.fuga.b) # 11
print(get_instance.fuga.c) # 12
まとめ
僕が考えた最強のConfigParser特化クラスとその使い方について解説をしました。
このコードの何がいいかというと
- iniファイルの情報を無駄に持つことがなくなる。
- クラス化を行うことで、iniファイルの情報を一つに集約ができる。
- 最初にインスタンス作成すれば、get_instanceでインスタンスを取得できるので、iniのパスをいろんなところに持たせなくて済む。
- iniのキーに対応した属性の追加を自動で対応してくれる。
- iniのセクションも属性としてアクセスができる。
- iniの値はread専用である。
などなどのメリットがあります。個人的な見解ですが。
個人的にはこのコードを使用していく方針ですが、
これ以外にもいい方法があれば、ご連絡いただけると助かります。
quiitaの記事初投稿してみた。