私が歌川です

@utgwkk が書いている

section無しのINIファイルをPythonのconfigparserで読み書きしたい

Pythonの標準ライブラリであるconfigparserを使うと、INIファイル っぽいもの を読み書きすることができます。が、セクションより先に key=value が来ると configparser.MissingSectionHeaderError 例外が送出されパースに失敗します。

読み出すときはINIファイルの先頭にダミーのセクションをくっつける、ということができそうだけど、書き出すときはどうしたらいいのか。実装を読んでみると、INIファイルを文字列やファイルから読み込むときは _read メソッドを、ファイルに書き出すときは _write_section メソッドを経由するように見えます。これらのメソッドをoverrideして、ダミーのセクションを付けたり外したりするとどうか、というアイデアで以下の実装を考えました。

実装

import configparser
import itertools

RESERVED_NO_SECTION = '___RESERVED_NO_SECTION'

# ref: https://github.com/python/cpython/blob/802ff7c0d339376a1b974e57d2caca898310de3d/Lib/configparser.py
class SectionlessConfigParser(configparser.ConfigParser):
    def _read(self, fp, fpname):
        chained_fp = itertools.chain([f'[{RESERVED_NO_SECTION}]'], fp)
        super()._read(chained_fp, fpname)

    def _write_section(self, fp, section_name, section_items, delimiter):
        # diff: treat section '___RESERVED_NO_SECTION' as no section
        if section_name != RESERVED_NO_SECTION:
            fp.write("[{}]\n".format(section_name))

        for key, value in section_items:
            value = self._interpolation.before_write(self, section_name, key,
                                                     value)
            if value is not None or not self._allow_no_value:
                value = delimiter + str(value).replace('\n', '\n\t')
            else:
                value = ""
            fp.write("{}{}\n".format(key, value))
        fp.write("\n")


ini_content = '''
key1=aaa

[section1]
key11=bbb
'''
config = SectionlessConfigParser()
config.read_string(ini_content)
print(dict(config[RESERVED_NO_SECTION]))
config.set(RESERVED_NO_SECTION, 'key1', 'poe~~~')

with open('out.ini', 'wt') as f:
    config.write(f)

with open('out.ini', 'rt') as f:
    print(f.read())

実行結果

たしかにセクションがないところも読み込めていそうに見えます。

% python3 read.py
{'key1': 'aaa'}
key1 = poe~~~

[section1]
key11 = bbb

感想

undocumentedなプライベートメソッドをoverrideしてるのでそのうち壊れそう!!!! configparser.ConfigParser クラスのコンストラクタの引数でうまく挙動を制御できないかと思ったけど、実装を読む限りセクション名は必須のようなのでこれぐらいしか思い付かなかったです。 なんかいい方法があったら教えてください。

あと、INIファイルの形式を規定する文章があったら教えてください*1

*1:ない気がしている