三、ConfigParser/configparser模块 1. 模块初识
ConfigParser模块在Python3中已经被重命名为configparser,从模块名称上就可以看出这是一个配置解析器模块。那么这里哟两个问题:
问题1:这个模块用于处理什么格式的配置文件?
它用于处理(读写)与Windows上的INI文件结构相似的文件,其格式类似于这样:
[DEFAULT] ServerAliveInterval = 45 Compression = yes CompressionLevel = 9 ForwardX11 = yes [bitbucket.org] User = hg [topsecret.server.com] Port = 50022 # Enable X11 forward protocol or not? ForwardX11 = no可看出,则个文件由3个组成部分:
组成部分名称描述实例[section] 相当于一个分组 [DEFAULT]、 [bitbucket.ogrg]
options 每个分组下面包含0个多个的key = value形式的选项,也可以这样写key : value User = hg, Port = 50022
comment 注释,说明性文字;默认以“#”号或“;”号开头的行 # Enable X11 forward protocol or not?
说明: [DEFAULT]是一个特殊的section,它为其它section提供它所包含的option的默认值。也就是说其它section默认会继承[DEFAULT]下所有option的值,但是各个section可以覆盖这些默认值。换句话说,在获取某个section下不存在但是在[DEFAULT]下存在的option的值时,获取的就是[DEFAULT]下这个option的值。
搞运维的同学会说,这跟mysql和php的配置文件结构一样。是的,这种格式的文件已经被广泛使用。
问题2:Python开发的什么场景下会用到这个模块?
当我们需要编写一个允许用户自定义某些功能参数的Python程序时就会用到这个模块。这其实很好理解,想想mysql和php的配置文件的作用就明白了。php和mysql就相当于我们自己编写的Python程序,它们允许我们这些用户通过配置文件(my.cnf和php.ini)来自定义一些参数从而影响php和mysql的运行。显然,php和mysql的实现程序中必然要有相应的代码来解析这些配置文件中的参数值。而在编写Python程序时,这个解析配置文件的工作就可以由ConfigParser/configparser模块来完成。只是ConfigParser/configparser模块不仅仅可以解析配置信息,还可以创建、修改和删除配置信息。
2. 如果让你来开发这个模块,你会提供哪些功能?这个配置文件很简单,只有3个组成部分:section、options、comment,因此这个模块提供的功能应该就是对这3个组成部分的增、删、改、查操作。那么我们至少应该提供以下几个函数:
功能模拟函数名添加一个section add_section(section_name)
在一个section下添加或修改一个option set_option(section_name, option_name, option_value)
删除指定的section remove_section(section_name)
删除一个section下的某个option remove_option(section_name, option_name)
返回所有section的列表 list_sections()
返回指定section下所有option名称列表 list_option_keys(section_name)
返回包含指定setcion下所有option的字典 list_option_items(section_name)
获取指定section下的指定option的值 get_option(section_name,option_name)
好的,思考结束,下面来看看大神们是怎么做的。
3. Python 2中的ConfigParser模块 模块概述Python 2中的ConfigParser模块提供了3个相关的类,这3各类是依次的继承关系,下面每一层缩进都表示一层继承关系:
class RawConfigParser class ConfigParser class SafeConfigParser它们提供的功能是依次增强的:
类名描述RawConfigParser 提供配置文件读写相关功能、不支持格式字符串替换
ConfigParser 允许取当前section或DEFAULT下option的值进行格式字符串替换
SafeConfigParser 允许取其他section下option的值进行格式字符串替换
格式字符串替换 是指一个option的值中可以包含并动态引用其它option的值,如:
base_dir = /data/wwww project_dir = %(base_dir)s/myproject对于上面这个实例,同上以上3个类对于project_dir值中的base_dir变量的查找过程如下:
类名格式字符串查找过程project_dir的值RawConfigParser 不进行格式字符串的查找 %(base_dir)s/myproject
ConfigParser 尝试分别从 当前section -> [DEFAULT] 查找base_dir的值 /data/www/myproject
SafeConfigParser 尝试分别从 当前section -> [DEFAULT] -> 其他section 查找base_dir的值 /data/www/myproject
各个类的构造方法:
class ConfigParser.RawConfigParser([defaults[, dict_type[, allow_no_value]]]) class ConfigParser.ConfigParser([defaults[, dict_type[, allow_no_value]]]) class ConfigParser.SafeConfigParser([defaults[, dict_type[, allow_no_value]]])可见,这几个类的构造方法中的参数都是可选参数:
defaults: 如果该参数被提供,它将被初始化为一个默认值的字典。
dict_type: 如果该参数被提供,她将被用于为section列表、一个section中的options以及默认选项创建一个该类型的字典对象。该参数于 Python 2.6被添加,Python 2.7时其默认值改为collections.OrderedDict。
allow_no_value: 表示是否允许option没有值的情况发生(此时获取该选项的值为None),默认值为False,表示不允许。该参数于Python 2.7被添加。
ConfigParser.RawConfigParser类与 section 相关的方法:
# 添加一个section。 # 如果该section名称已经存在,将会引发DuplicateSectionError; # 如果name参数的值是`DEFAULT`或任何它的大小写不敏感的变体(如:default、Default),将会引发ValueError add_section(section) # 删除指定的section,如果该section存在则返回True, 否则返回False remove_section() # 返回一个所有可用section的列表,但是不包括`DEFAULT` sections() # 判断指定的section是否已经存在,返回True或False has_section(section)与 option 相关的方法:
# 添加或修改某个已存在的section下option的值,如果section不存在将会引发NoSectionError。 set(section, option, value) # 获取某个section下指定option的值, 返回值为str,如果需要其他数据类型需要自己进行转换 get(section, option) # 对get()方法的字符串结果强制转换为整数并返回 getint(section, option) # 对get()方法的字符串结果强制转换为浮点数并返回 getfloat(section, option) # 对get()方法的字符串结果转换为布尔值并返回,但是这里并不是简单的数据类型的转换: # "1", "yes", "true", "on" 将会返回True # “0”, “no”, "false", "off" 将会返回False getboolen(section, option) # 删除指定section下的某option # 如果section不存在将会引发NoSectionError;如果存在将被删除的option则返回Ture,否则返回False remove_option(section, option) # 返回一个包含默认值的字典--即`[DEFAULT]`下所有option defaults() # 返回一个指定section下所有可用options的key的列表 options(section) # 返回一个由指定section中包含的每个option的(key, value)对组成的列表 items(section) # 判断某个指定的section下是否包含某个option # 如果指定的section存在,并且包含这个option,则返回True,否则返回False has_option(section, option) # 将指定的option名字转换为内部使用的格式,默认实现是转换为小写。可以通过子类和设置实例属性这两种方法来改变其默认行为,如`cfgparser.optionxform = str`则保持原样。 optionxform(option)配置文件读写相关方法:
# 读取并解析文件对象中的配置数据到当前实例中,如果filename被忽略且fp有一个name属性,则filename将会取这个属性值 readfp(fp[, filename]) # 将当前实例中的配置数据写入指定的文件对象中 write(fileobject) # 尝试读取并解析一个文件列表,然后返回一个被成功解析的文件列表。filenames可以是一个字符串,也可以是一个文件名列表: # 如果filenames是一个字符串或Unicode字符串,它将会被当做一个单数的文件; # 如果filenames列表中的某个文件不能被打开,该文件将会被忽略; # 该方法的设计目的在于让用户可以指定一个可能的配置文件位置列表,所有存在的配置文件都会被读取,如果任何一个配置文件都不存在,则ConfigParser实例将会包含一个空的数据集。 # 官方文档给出的建议是:如果一个应用需要从一个配置文件加载初始参数值,应该线使用readp()方法加载必要文件中的数据,然后再调用read()方法加载可选配置文件中的数据。 read(filenames)总结: readp()用于读取必要配置文件,read()用于读取可选配置文件。
ConfigParser.ConfigParser类首先,ConfigParser类是RawConfigParser的子类,所以它继承了RawConfigParser的以上方法;然后,ConfigParser类相对于RawConfigParser类的增强功能在于,它支持option值的格式化替换。为了向后兼容,允许用户关闭对option值的格式化替换功能,它重写了以下两个方法:
# 获取指定section下某个otpion的值 get(section, option[, raw[, vars]]) # 返回一个由指定section下的每个option的(key, value)对组成的列表 items(section[, raw[, vars]])可以看出,这两个方法的参数列表与RawConfigParser类相相应方法相比多了两个参数:
raw: 这是一个布尔值,主要是为了向后兼容。该参数默认值为False,所有的option值中包含的'%'格式字符串都会被替换为相应的值;如果该参数值为True,则不对这些格式字符串做处理,直接返回。
vars: 这是一个字典,可以用来临时定义某个被格式字符串引用的option的值,此时被格式字符串应用的option的查找过程是: vars(如果被提供) -> 当前section -> DEFAULT
SafeConfigParser类只是在ConfigParser类的基础上最set()方法做了一些限制:
添加或修改某个已存在的section下option的值,如果section不存在将会引发NoSectionError。 set(section, option, value)但是它要求option的value必须是一个字符串(str或unicode),否则将会引发TypeError。
实例 实例1:创建一个配置文件 import ConfigParser as CP # 这里用RawConfigParser、ConfigParser和SafeConfigParser都是一样的效果 config = CP.RawConfigParser({'BAZ': 'hard', 'Bar': 'Life'}) config.add_section('Section1') config.set('Section1', 'an_int', '15') config.set('Section1', 'a_float', '3.1415') config.set('Section1', 'a_bool', 'true') config.add_section('Section2') config.set('Section2', 'baz', 'fun') config.set('Section2', 'bar', 'Python') config.set('Section2', 'foo', '%(bar)s is %(baz)s!') with open('example.cfg', 'wb') as configfile: config.write(configfile)生成一个名为 example.cfg的文件,内容如下:
[DEFAULT] bar = Life baz = hard [Section1] an_int = 15 a_float = 3.1415 a_bool = true [Section2] baz = fun bar = Python foo = %(bar)s is %(baz)s!说明:
option的key被写入文件时默认会被转换成小写形式。
[DEFAULT]这个section是不能、也不需要通过config.add_section('DEFAULT')的方式添加的,默认就存在。
实例2:读取一个已存在的配置文件 >>> config = CP.RawConfigParser() >>> config.read('example.cfg') ['example.cfg'] >>> >>> config.sections() # 结果是不包含'DEFAULT'的 ['Section1', 'Section2'] >>> >>> config.has_section('Section1') True >>> config.has_section('Section11') False >>> config.has_section('DEFAULT') # 注意这里 False >>> config.has_section('Default') False >>> >>> config.defaults() OrderedDict([('bar', 'Life'), ('baz', 'hard')]) >>> >>> config.options('Section1') # 包括[DEFAULT]下的option ['an_int', 'a_float', 'a_bool', 'bar', 'baz'] >>> >>> config.items('Section1') [('bar', 'Life'), ('baz', 'hard'), ('an_int', '15'), ('a_float', '3.1415'), ('a_bool', 'true')] >>> >>> config.has_option('Section1', 'an_int') True >>> >>> config.has_option('Section1', 'two_int') False >>> >>> config.get('Section1', 'an_int') '15' >>> config.getint('Section1', 'an_int') 15 >>> config.getfloat('Section1', 'a_float') 3.1415 >>> >>> config.get('Section1', 'a_bool') 'true' >>> >>> config.getboolean('Section1', 'a_bool') True >>> >>> config.get('Section2', 'Foo') # 重点在这里 '%(bar)s is %(baz)s!'要想支持格式化字符串替换,就只能使用ConfigParser或SafeConfigParser类:
>>> import ConfigParser as CP >>> >>> config = CP.ConfigParser() >>> config.read('example.cfg') ['example.cfg'] >>> >>> config.get('Section2', 'foo') # %(bar)s和%(baz)s取[Section2]所包含的option的值 'Python is fun!' >>> >>> config.get('Section2', 'foo', raw=True) # 不进行格式字符串替换 '%(bar)s is %(baz)s!' >>> >>> config.get('Section2', 'foo', vars={'baz': 'busy', 'bar': 'Job'}) # %(bar)s和%(baz)s取vars参数所包含的option的值 'Job is busy!' >>> >>> config.remove_option('Section2', 'baz') True >>> config.remove_option('Section2', 'bar') True >>> config.get('Section2', 'foo') # %(bar)s和%(baz)s取[DEFAULT]所包含的option的值 'Life is hard!'如果已存在的配置数据中包含没有值的option,需要在常见解析器实例时指定allow_no_value=True :
>>> import ConfigParser as CP >>> import io >>> >>> sample_config = """ ... [mysqld] ... user = mysql ... pid-file = /var/run/mysqld/mysqld.pid ... skip-external-locking ... old_passwords = 1 ... skip-bdb ... skip-innodb ... """ >>> config = CP.RawConfigParser(allow_no_value=True) >>> config.readfp(io.BytesIO(sample_config)) >>> config.get('mysqld', 'user') 'mysql' >>> >>> config.get('mysqld', 'skip-bdb') # 返回None >>> >>> config.get('mysqld', 'does-not-exist') # 获取不存在的option会报错 Traceback (most recent call last): File "<stdin>", line 1, in <module> File "C:\Python27\lib\ConfigParser.py", line 340, in get raise NoOptionError(option, section) ConfigParser.NoOptionError: No option 'does-not-exist' in section: 'mysqld' >>> 实例3:修改配置文件貌似大神们没有提供相应的方法来实现将一个section下的option移动到另外一个section下的功能,那么我们自己来实现它。
思路很简单: 取出原section下要移动的option的值 --> 在目标section(如果不存在则添加该section)下添加该option --> 从原section中删除移动的option
def move_option(config, section_from, section_to, option): try: config.set(section_to, option, config.get(section_from, option)) except ConfigParser.NoSectionError: config.add_section(section_to) move_option(config, section_from, section_to, option) else: config.remove_option(section_from, option)有人会问,如果原section不存在,或者要移动的option不存在怎么办?个人觉得这些判断在调用这个函数之前进行处理会更好,这个函数只处理移动操作:
>>> import ConfigParser >>> >>> config = ConfigParser.ConfigParser() >>> config.read('example.cfg') ['example.cfg'] # 查看当前已存在的section以及各section下所包含的option >>> config.sections() ['Section1', 'Section2'] >>> config.items('Section1') [('bar', 'Life'), ('baz', 'hard'), ('an_int', '15'), ('a_float', '3.1415'), ('a_bool', 'true')] >>> config.items('Section2') [('bar', 'Python'), ('baz', 'fun'), ('foo', 'Python is fun!')] # 将Section1下的an_int移动到已经存在的Section2下 >>> move_option(config, 'Section1', 'Section2', 'an_int') # 将Section1下的a_float移动到不存在的Section3下 >>> move_option(config, 'Section1', 'Section3', 'a_float') # 查看当前已存在的section以及各section下所包含的option >>> config.sections() ['Section1', 'Section2', 'Section3'] >>> config.items('Section1') [('bar', 'Life'), ('baz', 'hard'), ('a_bool', 'true')] >>> config.items('Section2') [('bar', 'Python'), ('baz', 'fun'), ('foo', 'Python is fun!'), ('an_int', '15')] >>> config.items('Section3') [('bar', 'Life'), ('baz', 'hard'), ('a_float', '3.1415')] # 移除Section1 >>> config.remove_section('Section1') True # 将当前配置数据写回配置文件 with open('example.cfg', 'wb') as configfile: config.write(configfile)此时,配置文件的内容已被修改为:
[DEFAULT] bar = Life baz = hard [Section2] baz = fun bar = Python foo = %(bar)s is %(baz)s! an_int = 15 [Section3] a_float = 3.1415