LoginSignup
1
1

More than 1 year has passed since last update.

python+Configparser

Last updated at Posted at 2021-05-27

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の記事初投稿してみた。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1