雑食性雑感雑記

知識の整理場。ため込んだ知識をブログ記事として再構築します。

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