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。
INIファイルの形式を規定した文章ってどこかに転がってないかな
— うたがわきき (@utgwkk) 2020年11月18日
*1:ない気がしている