总结configparser模块的使用

-- TOC --

Python标准库中的configparser模块,是专门用来读写配置文件的模块,我们常使用后缀为ini的文件来表示配置文件。据说ini配置文件最早出现在Win系统中,是Initialization File的缩写 ,早已成为一个比较通用的格式,但据说现在python的new best friend,是TOML。(参考:JSON,YAML和TOML

配置文件格式支持

配置文件是一个文本文件,文件内有用方括号[]括起来的区域,称为section,以及该区域内的key/value对。section的名称是case sensitive的,即大小写相关;而key的名称则大小写无关。一般section的name都用大写,key name都用小写。

key/value使用等号=或者冒号:进行连接,key和value都是string,如果需要别的类型,要自己在代码中进行转换。因为都是string,key和value的中间可以有空格,但是两边的空格则会被忽略。value还可以多行,换行后要有前置的空格,表示这是一个换行处。

在配置文件中,还可以有注释,以#;开头的行,都是注释。在解析的时候,注释会被直接忽略掉。注释是一独立行!

下面是一个Python configparser模块支持的ini配置文件的例子:

# file name: example.ini
[DEFAULT]
item1 = 1
ITEM2 = 2
Item3 = 3
item4=4
item5 : 5
item6:6
item 7 = 7 7 7
port = 12345
; this is also comments
sth = 

[SERVER]
server = py.maixj.net
#port = 12345
name = i don't want to 
  tell you my name, haha...

[WEB]
ITEM2=222.222
web server = apache
alternative = nginx

我们来写点代码解析它:

>>> import configparser
>>> 
>>> config = configparser.ConfigParser()
>>> config.read('example.ini')
['example.ini']
>>> config.sections()
['SERVER', 'WEB']

这个example.ini配置文件,有一个DEFAULT区域,这个区域有一点点特别,它保存了配置项的默认值,但不会出现在解析后的sections中。当某个配置项只在DEFAULT中存在是,读取这个配置项,读到的就是DEFAULT中的值,如果此配置项还同时存在于其它section中,其它section中保存的值,优先级更高。如下示例,读取port和ITEM2:

>>> config['SERVER']['port']
'12345'
>>> config['WEB']['port']
'12345'
>>> config['SERVER']['ITEM2']
'2'
>>> config['WEB']['ITEM2']
'222.222'

section name大小写有关,而具体的配置项key name大小写无关:

>>> config['WEB']['web server']
'apache'
>>> config['WEB']['WEB SERVER']
'apache'

再看看空格,换行和没有value的配置项:

>>> config['WEB']['item 7']
'7 7 7'
>>> config['SERVER']['name']
"i don't want to\ntell you my name, haha..."
>>> print(config['SERVER']['name'])
i don't want to
tell you my name, haha...
>>> config['SERVER']['sth']  # sth=, = is must
''

前文已经有所叙述,配置文件中的数据默认都是string,如果需要其它类型,要自己在代码中转化。同时,configparser模块也还是提供了几个很顺手的函数接口,专门处理数据类型转换。有 get()getint()getfloat()getboolean() 这四个。get()函数是通用的,读出来的是string。

>>> config.get('WEB', 'web server')
'apache'
>>> config.getint('WEB', 'item5')
5
>>> config.getfloat('WEB', 'item2')
222.222
>>> config.getboolean('WEB', 'item1')
True

注意getboolean()函数,配置项的值如果是 1, yes, on, true,使用getboolean()函数读出来的就是True;配置项的值如果是 0,no,off,false,读出来的就是False。

传统的配置文件,都是字符串,配置文件格式本身缺少对数据类型的支持。而新的TOML在这一点上就很不一样。

config对象的数据组织很像dict对象,可以像遍历dict对象那样编译config对象。

还有另外一种读取配置数据的方式:

>>> config_web = config['WEB']
>>> config_web.get('web server')
'apache'

使用get系列接口,还可以提供fallback值,在配置项缺失的时候,不至于raise:

>>> config_web.get('host')  # default fallback to None
>>> config_web.get('host', 'aws')
'aws'
>>> config.get('WEB', 'host', fallback=None)
>>> config.get('WEB', 'host', fallback='aws')
'aws'

注意:config对象(含多个section)与config_web对象(某个具体的section),在使用get的时候,参数不一样!

上面的示例,都是关于如何读取配置文件。下面示例如何写配置文件,在需要自动更新配置文件的时候有用:

>>> import configparser
>>> config = configparser.ConfigParser()
>>> config['DEFAULT'] = {'A':1,'B':2,'C':3}
>>> config['Server'] = {}
>>> config['Server']['A'] = '5'
>>> config['Server']['B'] = '6'
>>> config['Server']['D'] = '8'
>>> with open('anotest.ini','w') as inifile:
...     config.write(inifile)

然后,就得到了anotest.ini文件:

$ cat anotest.ini
[DEFAULT]
a = 1
b = 2
c = 3

[Server]
a = 5
b = 6
d = 8

如果要清除某个section下的配置项,调用clear接口。如:

config['Server'].clear()

以上内容,对于使用configparser模块处理配置文件,基本足够。

从str或dict中读取配置

configparser模块除了支持从配置文件读取配置外,还可以从str对象和dict对象读取配置。

从str对象读取配置:

>>> configstr = """
... [Server]
... addr = 1.2.3.4
... domain = py.maixj.net
... [MISC]
... port = 12345
... log = on
... """
>>> config = configparser.ConfigParser()
>>> config.read_string(configstr)
>>> config.sections()
['Server', 'MISC']

从dict对象读取配置:

>>> config_dict = {'Server':{'addr' : '1.2.3.4',
...                          'domain' : 'py.maixj.net'},
...                'MISC' : {'port' : 12345,
...                          'log' : 'on'}}
>>> config = configparser.ConfigParser()
>>> config.read_dict(config_dict)
>>> config.sections()
['Server', 'MISC']
>>> config['MISC'].getboolean('log')
True

allow_no_value

如果我们的配置文件长这样:

$ cat example.ini
[ABC]
abc

abc配置项后面连等号都没有,此时这个配置项就是no value。默认情况下,这是个错误的配置文件,configparser模块在读取的时候,会raise。不过,我们增加一个参数来支持这种情况:

>> import configparser
>>> config = configparser.ConfigParser(allow_no_value=True)
>>> config.read('example.ini')
['example.ini']
>>> config.get('ABC', 'abc')
>>> config.get('ABC', 'abc') is None
True

度出来的no value配置项,其value为None。

配置项的interpolation

basic interpolation

>>> config_str = """
... [Path]
... home = /home/xinlin
... video = %(home)s/Video
... movie = %(video)s/Movie
... """
>>> config = configparser.ConfigParser()
>>> config.read_string(config_str)
>>> config['Path']['home']
'/home/xinlin'
>>> config['Path']['video']
'/home/xinlin/Video'
>>> config['Path']['movie']
'/home/xinlin/Video/Movie'

所谓basic initerpolation,即这种插入替换只能出现在相同的section,不能跨区,正如上面的代码,home与video,audio在同一个Path区域。插入替换home值的方式是:%(home)s。上面的代码,实现的是多层插入替换,movie的值来之video,video的值又来自home。

extended interpolation

可以跨区,而且语法上也有区别。Basic和Extended两种方式的语法不兼容。下面是Extended Interpolation的代码示例:

>>> config_str = """
... [Home]
... home = /home/xinlin
... [Lib]
... lib = ${Home:home}/Lib
... gcc_lib = ${lib}/gcc
... [DEFAULT]
... binpath = /usr/bin
... [Path]
... path = ${binpath}/secret
... """
>>> from configparser import ConfigParser, ExtendedInterpolation
>>> config = ConfigParser(interpolation=ExtendedInterpolation())
>>> config.read_string(config_str)
>>> config['Lib']['lib']
'/home/xinlin/Lib'
>>> config['Lib']['gcc_lib']
'/home/xinlin/Lib/gcc'
>>> config['Path']['path']
'/usr/bin/secret'

配置文件读写接口

下面是我一直在使用的两个读写ini配置文件的接口函数,供参考:

def readConfig(cfile, section, name):
    """Return None or config value by specifying cfile, section and name.
    cfile is formatted according to configparser module.
    """
    config = configparser.ConfigParser()
    config.read(cfile)
    return config.get(section, name, fallback=None)
    #try:
    #    config = configparser.ConfigParser()
    #    config.read(cfile)
    #    rv = config[section][name]
    #except KeyError:
    #    return None
    #else:
    #    return rv


def writeConfig(cfile, section, name, value):
    """Add or update an configurable item in config file.
    Create config file (cfile) if it is not existed,
    cfile is formatted according to configparser module.
    """
    config = configparser.ConfigParser()
    config.read(cfile)  # no raise if file isn't existed.
    if section not in config:
        config[section] = {}
    config[section][name] = value
    with open(cfile+'.config.tmp', 'w') as f:
        config.write(f)
    try:  # in case cfile is not existed
        os.remove(cfile)
    except FileNotFoundError:
        pass
    os.rename(cfile+'.config.tmp', cfile)

flake8的tox.ini配置文件

flake8的配置文件,默认文件名为tox.ini,其实也是configparser模块支持的格式。例如:

$ cat tox.ini
[flake8]
format = %(path)s: %(row)d,%(col)d: %(code)s: %(text)s
exclude =
    .git,
    __pycache__,
    README.md,
    LICENSE
ignore =
    # E127: continuation line over-indented for visual indent
    E127,
    # E225,E226,E227,E228: missing whitespace around somewhere
    E225,
    E226,
    E227,
    E228,
    # E231: missing whitespace after ','
    E231,
    # E241: multiple spaces after ','
    E241,
    # E701: multiple statements on one line (colon)
    E701,
    # W391: blank line at end of file
    W391,
    # W503,W504: line break before binary operator
    W503,
    W504

可以在不断地换行中注释!

针对这个tox.ini文件,在使用configparser模块读取的时候,要设置get接口的format这个option:

>>> import configparser
>>> 
>>> config = configparser.ConfigParser()
>>> config.read('tox.ini')
['tox.ini']
>>> config.get('flake8','format',raw=True)
'%(path)s: %(row)d,%(col)d: %(code)s: %(text)s'
>>>
>>> config['flake8']['exclude']
'\n.git,\n__pycache__,\nREADME.md,\nLICENSE'
>>> config['flake8']['ignore']
'\nE127,\nE225,\nE226,\nE227,\nE228,\nE231,\nE241,\nE701,\nW391,\nW503,\nW504'

如果不使用raw=True读取,会raise。

本文链接:https://cs.pynote.net/sf/python/202203131/

-- EOF --

-- MORE --