PythonのConfigParserでカッチリとしたコンフィグ設定をする
概要
- Pythonのモジュール「ConfigParser」を使うと、設定ファイルをパースして使えて便利!!
- …なのだが、値は全て文字列なので、そこから適切な形に変換しないと――。
- 別途設定ファイルのための設定を作り、制御できるようにしてみた。
ConfigParser
- Pythonの標準モジュールの1つ。Windows ini ファイル (っぽい) 形式のファイル (以降、設定ファイル)をパースし、読み込んでくれる。
- 今から使うなら、「ConfigParser」よりも「SafeConfigParser」使った方が良い。
- 便利なんだけど、パース結果は全て文字列。
- 数値や配列が良い場合は自分で変換する必要あり。
今回の目標
- コード内で、設定ファイルの設定を作成し、『設定ファイルの制御』ができるようにする。
- コード量は増えるけど、安全性は増すハズ。
- 合わせて、設定ファイルの設定を行うことで、簡単に数値や配列等へ値を変換できるようにする。
- 更に「設定必須」「オプション」の区分も付けてみる。
サンプル
設定ファイル (settings.conf)
- 文字列、整数、文字列の配列、None を入力したい。
[user] owner: kazuki age: 29 nickname: Kazu tel: None [cat] species: Japanese Bobtail Abyssinian Norwegian Forest Cat Russian Blue 三毛 雑種
設定ファイルの設定 (main.py)
- 「config_settings」という変数に定義
- 「セクション → オプション」の形で定義する。
- name : 各セクション内のオプション名
- type : 該当の型への変換関数。str_list は存在しないので、独自定義。
- required : True の場合は必須。設定ファイル内に存在しなければエラー出したい。
- str_list は文字列を改行で区切って配列化する関数。
def str_list(v) : return [x for x in v.split('\n') if len(x) != 0] config_settings = { 'user': [ {'name': 'owner', 'type': str, 'required': True}, {'name': 'age', 'type': int, 'required': True}, {'name': 'nickname', 'type': str, 'required': False}, {'name': 'tel', 'type': str, 'required': True}, ], 'cat': [ {'name': 'species', 'type': str_list, 'required': True}, ], }
設定ファイル変換関数 (main.py)
- 「configParse」関数で実施
- ファイルチェックを行い、設定ファイルが無ければエラー。
- 中のデータを全て辞書型に変換。
- config_settings の内容ごと、存在チェックと変換。
import os from ConfigParser import SafeConfigParser def configParse(file_path) : ### File check if not os.path.exists(file_path) : raise IOError(file_path) parser = SafeConfigParser() parser.read(file_path) ### Convert to dictionary config = {} for sect in parser.sections() : config[sect] = {} for opt in parser.options(sect) : config[sect][opt] = parser.get(sect, opt) ### Data check and convert from type for sect in config_settings.keys() : # セクション存在チェック if not sect in config : raise KeyError(sect) for opt_attr in config_settings[sect] : # 必須属性チェック if opt_attr['required'] and (not opt_attr['name'] in config[sect]) : raise KeyError(opt_attr['name']) # 変換 if config[sect][opt_attr['name']] == 'None' : config[sect][opt_attr['name']] = None else : config[sect][opt_attr['name']] = opt_attr['type'](config[sect][opt_attr['name']]) return config
使ってみる (main.py)
- Windows 上で実行
- cp932 への変換を行っているのは OS を考慮して。
- もちろん、Linux でも動きます。
def _c(array) : text = "" for a in array : text += a.decode("utf-8").encode("cp932") + "|" return text[0:len(text)-1] if __name__ == '__main__' : conf_file = "settings.conf" config = None try : config = configParse(conf_file) except IOError as e : print "Config file \"{0}\" is not found.".format(e) quit() except KeyError as e : print "Config key \"{0}\" is not found.".format(e) quit() except Exception as e : print "Exception occurred: {0}".format(e) quit() print "OK" print "Print config..." for sect, data in config.items() : for k, v in data.items() : if (type(v) == type([])) : v = _c(v) print sect, ", ", k, ", ", v
- 実行結果
>python config_read.py OK Print config... user , owner , kazuki user , tel , None user , age , 29 user , nickname , Kazu cat , species , Japanese Bobtail|Abyssinian|Norwegian Forest Cat|Russian Blue|三毛|雑種
まとめ
所感
- 設定ファイルの変更が必要になった場合に面倒!!
- 設定ファイル自体の書き換えと、設定ファイルの設定の書き換えが必要になる。
- 簡単にConfigParser使うなら、今回のは作り込みだと思うけど。。
- でも、安全性を求めるなら――。
(main.py 全体)
# -*- coding: utf-8 -*- def str_list(v) : return [x for x in v.split('\n') if len(x) != 0] config_settings = { 'user': [ {'name': 'owner', 'type': str, 'required': True}, {'name': 'age', 'type': int, 'required': True}, {'name': 'nickname', 'type': str, 'required': False}, {'name': 'tel', 'type': str, 'required': True}, ], 'cat': [ {'name': 'species', 'type': str_list, 'required': True}, ], } import os from ConfigParser import SafeConfigParser def configParse(file_path) : ### File check if not os.path.exists(file_path) : raise IOError(file_path) parser = SafeConfigParser() parser.read(file_path) ### Convert to dictionary config = {} for sect in parser.sections() : config[sect] = {} for opt in parser.options(sect) : config[sect][opt] = parser.get(sect, opt) ### Data check and convert from type for sect in config_settings.keys() : # セクション存在チェック if not sect in config : raise KeyError(sect) for opt_attr in config_settings[sect] : # 必須属性チェック if opt_attr['required'] and (not opt_attr['name'] in config[sect]) : raise KeyError(opt_attr['name']) # 変換 if config[sect][opt_attr['name']] == 'None' : config[sect][opt_attr['name']] = None else : config[sect][opt_attr['name']] = opt_attr['type'](config[sect][opt_attr['name']]) return config def _c(array) : text = "" for a in array : text += a.decode("utf-8").encode("cp932") + "|" return text[0:len(text)-1] if __name__ == '__main__' : conf_file = "settings.conf" config = None try : config = configParse(conf_file) except IOError as e : print "Config file \"{0}\" is not found.".format(e) quit() except KeyError as e : print "Config key \"{0}\" is not found.".format(e) quit() except Exception as e : print "Exception occurred: {0}".format(e) quit() print "OK" print "Print config..." for sect, data in config.items() : for k, v in data.items() : if (type(v) == type([])) : v = _c(v) print sect, ", ", k, ", ", v