15.8. configparser — 配置文件操作

目的:读取/写入类似于Windows INI文件的配置文件

使用configparser模块来管理应用程序的用户可编辑配置文件。可以将配置文件的内容组织成组,并且支持几种选项值类型,包括整数,浮点值和布尔值。可以使用Python格式化字符串组合选项值,以从较短的值(例如主机名和端口号)构建较长的值(例如URL)。

配置文件格式

configparser使用的文件格式类似于旧版Microsoft Windows使用的文件格式。它由一个或多个命名的组成,每个节可以包含带有名称和值的单个选项

通过查找以configparser结尾的行来标识配置文件部分。方括号之间的值是节名称,并且可以包含除方括号之外的任何字符。

选项在节中每行列出一个。该行以选项名称开头,该名称与值之间用冒号()或等号(=)分隔。解析文件时,分隔符周围的空格将被忽略。

以分号(;)或octothorpe()开头的行被视为注释,并且以编程方式访问配置文件的内容时不可见。

该示例配置文件包含名为bug_tracker的部分,其中包含三个选项:url用户名密码

Lines starting with semi-colon (;) or octothorpe (#) are treated as comments and not visible when accessing the contents of the configuration file programmatically.

This sample configuration file has a section named bug_tracker with three options, urlusername, and password.

# This is a simple example with comments.
[bug_tracker]
url = http://localhost:8080/bugs/
username = dhellmann
; You should not store passwords in plain text
; configuration files.
password = SECRET

读取配置文件

配置文件最常见的用途是让用户或系统管理员使用常规文本编辑器编辑文件,以设置应用程序行为默认值,然后让应用程序读取文件,解析文件并根据其内容执行操作。使用ConfigParserread()方法读取配置文件。

configparser_read.py

from configparser import ConfigParser

parser = ConfigParser()
parser.read('simple.ini')

print(parser.get('bug_tracker', 'url'))

该程序从上一节中读取simple.ini文件,并从bug_tracker部分中打印url选项的值。

$ python3 configparser_read.py

http://localhost:8080/bugs/

read()方法也接受文件名列表。依次扫描每个名称,如果文件存在,则将其打开并读取。

configparser_read_many.py

from configparser import ConfigParser
import glob

parser = ConfigParser()

candidates = ['does_not_exist.ini', 'also-does-not-exist.ini',
              'simple.ini', 'multisection.ini']

found = parser.read(candidates)

missing = set(candidates) - set(found)

print('Found config files:', sorted(found))
print('Missing files     :', sorted(missing))

read()返回包含成功加载的文件名的列表,因此程序可以发现缺少的配置文件并决定是忽略它们还是将条件视为错误。

$ python3 configparser_read_many.py

Found config files: ['multisection.ini', 'simple.ini']
Missing files     : ['also-does-not-exist.ini',
'does_not_exist.ini']

Unicode配置数据

包含Unicode数据的配置文件应使用正确的编码值读取。以下示例文件将原始输入的密码值更改为包含Unicode字符,并使用UTF-8进行编码。

unicode.ini

[bug_tracker]
url = http://localhost:8080/bugs/
username = dhellmann
password = ßéç®é†

该文件将使用适当的解码器打开,将UTF-8数据转换为本地Unicode字符串。

configparser_unicode.py

from configparser import ConfigParser
import codecs

parser = ConfigParser()
# Open the file with the correct encoding
parser.read('unicode.ini', encoding='utf-8')

password = parser.get('bug_tracker', 'password')

print('Password:', password.encode('utf-8'))
print('Type    :', type(password))
print('repr()  :', repr(password))

get()返回的值是 Unicode 字符串,因此为了安全地打印它,必须将其重新编码为 UTF-8。

$ python3 configparser_unicode.py

Password: b'.c3.9f.c3.a9.c3.a7.c2.ae.c3.a9.e2.80.a0
'
Type    : <class 'str'>
repr()  : 'ßéç®é†'

访问配置设置

ConfigParser包括用于检查已解析配置的结构的方法,包括列出节和选项并获取其值。此配置文件包括两个部分,用于单独的Web服务。

[bug_tracker]
url = http://localhost:8080/bugs/
username = dhellmann
password = SECRET

[wiki]
url = http://localhost:8080/wiki/
username = dhellmann
password = SECRET

此示例程序将练习一些用于查看配置数据的方法,包括sections()options()items()

configparser_structure.py

from configparser import ConfigParser

parser = ConfigParser()
parser.read('multisection.ini')

for section_name in parser.sections():
    print('Section:', section_name)
    print('  Options:', parser.options(section_name))
    for name, value in parser.items(section_name):
        print('  {} = {}'.format(name, value))
    print()

sections()options()都返回字符串列表,而items()返回包含名称-值对的元组列表。

$ python3 configparser_structure.py

Section: bug_tracker
  Options: ['url', 'username', 'password']
  url = http://localhost:8080/bugs/
  username = dhellmann
  password = SECRET

Section: wiki
  Options: ['url', 'username', 'password']
  url = http://localhost:8080/wiki/
  username = dhellmann
  password = SECRET

ConfigParser还支持与 dict 相同的映射 API,ConfigParser充当一个字典,每个部分包含单独的字典。

configparser_structure_dict.py

from configparser import ConfigParser

parser = ConfigParser()
parser.read('multisection.ini')

for section_name in parser:
    print('Section:', section_name)
    section = parser[section_name]
    print('  Options:', list(section.keys()))
    for name in section:
        print('  {} = {}'.format(name, section[name]))
    print()

使用映射 API 访问相同的配置文件会产生相同的输出。

$ python3 configparser_structure_dict.py

Section: DEFAULT
  Options: []

Section: bug_tracker
  Options: ['url', 'username', 'password']
  url = http://localhost:8080/bugs/
  username = dhellmann
  password = SECRET

Section: wiki
  Options: ['url', 'username', 'password']
  url = http://localhost:8080/wiki/
  username = dhellmann
  password = SECRET

测试值是否存在

要测试某个节是否存在,请使用has_section()并传递节名称。

configparser_has_section.py

from configparser import ConfigParser

parser = ConfigParser()
parser.read('multisection.ini')

for candidate in ['wiki', 'bug_tracker', 'dvcs']:
    print('{:<12}: {}'.format(
        candidate, parser.has_section(candidate)))

在调用get()之前测试部分是否存在,以避免丢失数据的异常。

$ python3 configparser_has_section.py

wiki        : True
bug_tracker : True
dvcs        : False

使用has_option()来测试节中是否存在选项。
configparser_has_option.py

from configparser import ConfigParser

parser = ConfigParser()
parser.read('multisection.ini')

SECTIONS = ['wiki', 'none']
OPTIONS = ['username', 'password', 'url', 'description']

for section in SECTIONS:
    has_section = parser.has_section(section)
    print('{} section exists: {}'.format(section, has_section))
    for candidate in OPTIONS:
        has_option = parser.has_option(section, candidate)
        print('{}.{:<12}  : {}'.format(
            section, candidate, has_option))
    print()

如果该节不存在,则has_option()返回False

$ python3 configparser_has_option.py

wiki section exists: True
wiki.username      : True
wiki.password      : True
wiki.url           : True
wiki.description   : False

none section exists: False
none.username      : False
none.password      : False
none.url           : False
none.description   : False

值类型

所有节和选项名称都视为字符串,但是选项值可以是字符串,整数,浮点数或布尔值。存在一系列可能的布尔值,它们被转换为true或false。下面的示例文件包括每个。

types.ini

[ints]
positive = 1
negative = -5

[floats]
positive = 0.2
negative = -3.14

[booleans]
number_true = 1
number_false = 0
yn_true = yes
yn_false = no
tf_true = true
tf_false = false
onoff_true = on
onoff_false = false

ConfigParser不会尝试理解选项类型。期望应用程序使用正确的方法来获取所需类型的值。 get()始终返回一个字符串。将getint()用于整数,将getfloat()用于浮点数,将getboolean()用于布尔值。

configparser_value_types.py

from configparser import ConfigParser

parser = ConfigParser()
parser.read('types.ini')

print('Integers:')
for name in parser.options('ints'):
    string_value = parser.get('ints', name)
    value = parser.getint('ints', name)
    print('  {:<12} : {!r:<7} -> {}'.format(
        name, string_value, value))

print('.Floats:')
for name in parser.options('floats'):
    string_value = parser.get('floats', name)
    value = parser.getfloat('floats', name)
    print('  {:<12} : {!r:<7} -> {:0.2f}'.format(
        name, string_value, value))

print('.Booleans:')
for name in parser.options('booleans'):
    string_value = parser.get('booleans', name)
    value = parser.getboolean('booleans', name)
    print('  {:<12} : {!r:<7} -> {}'.format(
        name, string_value, value))

使用示例输入运行该程序将产生以下输出。

$ python3 configparser_value_types.py

Integers:
  positive     : '1'     -> 1
  negative     : '-5'    -> -5

Floats:
  positive     : '0.2'   -> 0.20
  negative     : '-3.14' -> -3.14

Booleans:
  number_true  : '1'     -> True
  number_false : '0'     -> False
  yn_true      : 'yes'   -> True
  yn_false     : 'no'    -> False
  tf_true      : 'true'  -> True
  tf_false     : 'false' -> False
  onoff_true   : 'on'    -> True
  onoff_false  : 'false' -> False

可以通过将converters 参数中的转换函数传递给ConfigParser来添加自定义类型转换器。每个转换器接收一个输入值,并将该值转换为适当的返回类型。

configparser_custom_types.py

from configparser import ConfigParser
import datetime

def parse_iso_datetime(s):
    print('parse_iso_datetime({!r})'.format(s))
    return datetime.datetime.strptime(s, '%Y-%m-%dT%H:%M:%S.%f')

parser = ConfigParser(
    converters={
        'datetime': parse_iso_datetime,
    }
)
parser.read('custom_types.ini')

string_value = parser['datetimes']['due_date']
value = parser.getdatetime('datetimes', 'due_date')
print('due_date : {!r} -> {!r}'.format(string_value, value))

添加转换器会导致ConfigParser使用转换器中指定的类型名称自动为该类型创建检索方法。在此示例中,'datetime'转换器导致添加新的getdatetime()方法。

$ python3 configparser_custom_types.py

parse_iso_datetime('2015-11-08T11:30:05.905898')
due_date : '2015-11-08T11:30:05.905898' -> datetime.datetime(201
5, 11, 8, 11, 30, 5, 905898)

也可以将转换器方法直接添加到ConfigParser的子类中。

选项作为标志

通常,解析器会为每个选项要求一个明确的值,但是将ConfigParser参数allow_no_value设置为True的情况下,选项本身会出现在输入文件,并用作标志。
configparser_allow_no_value.py

import configparser

# Require values
try:
    parser = configparser.ConfigParser()
    parser.read('allow_no_value.ini')
except configparser.ParsingError as err:
    print('Could not parse:', err)

# Allow stand-alone option names
print('.Trying again with allow_no_value=True')
parser = configparser.ConfigParser(allow_no_value=True)
parser.read('allow_no_value.ini')
for flag in ['turn_feature_on', 'turn_other_feature_on']:
    print('.', flag)
    exists = parser.has_option('flags', flag)
    print('  has_option:', exists)
    if exists:
        print('         get:', parser.get('flags', flag))

当选项没有显式值时,has_option()报告该选项存在,并且get() 返回 null

$ python3 configparser_allow_no_value.py

Could not parse: Source contains parsing errors:
'allow_no_value.ini'
        [line  2]: 'turn_feature_on.'

Trying again with allow_no_value=True

 turn_feature_on
  has_option: True
         get: None

 turn_other_feature_on
  has_option: False

多行字符串

如果缩进后几行,则字符串值可以跨越多行。

[example]
message = This is a multi-line string.
  With two paragraphs.

  They are separated by a completely empty line.

在缩进的多行值中,空白行被视为值的一部分并保留。

$ python3 configparser_multiline.py

This is a multi-line string.
With two paragraphs.

They are separated by a completely empty line.

修改设置

虽然ConfigParser主要是通过从文件中读取设置来配置的,但是也可以通过调用add_section()创建新节并` set()< aaaa>添加或更改选项。

configparser_populate.py

import configparser

parser = configparser.ConfigParser()

parser.add_section('bug_tracker')
parser.set('bug_tracker', 'url', 'http://localhost:8080/bugs')
parser.set('bug_tracker', 'username', 'dhellmann')
parser.set('bug_tracker', 'password', 'secret')

for section in parser.sections():
    print(section)
    for name, value in parser.items(section):
        print('  {} = {!r}'.format(name, value))

所有选项都必须设置为字符串,即使它们将以整数,浮点数或布尔值检索。

$ python3 configparser_populate.py

bug_tracker
  url = 'http://localhost:8080/bugs'
  username = 'dhellmann'
  password = 'secret'

可以使用remove_section()remove_option()ConfigParser中删除节和选项。

configparser_remove.py

from configparser import ConfigParser

parser = ConfigParser()
parser.read('multisection.ini')

print('Read values:.')
for section in parser.sections():
    print(section)
    for name, value in parser.items(section):
        print('  {} = {!r}'.format(name, value))

parser.remove_option('bug_tracker', 'password')
parser.remove_section('wiki')

print('.Modified values:.')
for section in parser.sections():
    print(section)
    for name, value in parser.items(section):
        print('  {} = {!r}'.format(name, value))

删除节将删除它包含的所有选项。

$ python3 configparser_remove.py

Read values:

bug_tracker
  url = 'http://localhost:8080/bugs/'
  username = 'dhellmann'
  password = 'SECRET'
wiki
  url = 'http://localhost:8080/wiki/'
  username = 'dhellmann'
  password = 'SECRET'

Modified values:

bug_tracker
  url = 'http://localhost:8080/bugs/'
  username = 'dhellmann'

保存配置文件

用所需的数据填充ConfigParser后,可以通过调用write()方法将其保存到文件中。这样就可以提供用于编辑配置设置的用户界面,而无需编写任何代码来管理文件。

configparser_write.py

import configparser
import sys

parser = configparser.ConfigParser()

parser.add_section('bug_tracker')
parser.set('bug_tracker', 'url', 'http://localhost:8080/bugs')
parser.set('bug_tracker', 'username', 'dhellmann')
parser.set('bug_tracker', 'password', 'secret')

parser.write(sys.stdout)

write()方法采用类似文件的对象作为参数。它以 INI 格式写出数据,因此可以通过ConfigParser再次对其进行解析。

$ python3 configparser_write.py

[bug_tracker]
url = http://localhost:8080/bugs
username = dhellmann
password = secret

Warning

读取,修改和重写配置文件时,原始配置文件中的注释不会保留。

选项搜索路径

ConfigParser在查找选项时使用多步搜索过程。

在开始选项搜索之前,必须对部分名称进行测试。如果该节不存在,并且名称不是特殊值DEFAULT,则引发NoSectionError

  1. 如果选项名称出现在传递给get()vars词典中,则返回vars的值。
  2. 如果选项名称出现在指定的部分,则返回该部分的值。
  3. 如果选项名称出现在DEFAULT部分中,则返回该值。
  4. 如果选项名称出现在传递给构造函数的默认值词典中,则返回该值。

如果在任何这些位置均未找到该名称,则会引发NoOptionError

使用此配置文件可以演示搜索路径行为。

[DEFAULT]
file-only = value from DEFAULT section
init-and-file = value from DEFAULT section
from-section = value from DEFAULT section
from-vars = value from DEFAULT section

[sect]
section-only = value from section in file
from-section = value from section in file
from-vars = value from section in file

该测试程序包括未在配置文件中指定的选项的默认设置,并覆盖文件中定义的某些值。

configparser_defaults.py

import configparser

# Define the names of the options
option_names = [
    'from-default',
    'from-section', 'section-only',
    'file-only', 'init-only', 'init-and-file',
    'from-vars',
]

# Initialize the parser with some defaults
DEFAULTS = {
    'from-default': 'value from defaults passed to init',
    'init-only': 'value from defaults passed to init',
    'init-and-file': 'value from defaults passed to init',
    'from-section': 'value from defaults passed to init',
    'from-vars': 'value from defaults passed to init',
}
parser = configparser.ConfigParser(defaults=DEFAULTS)

print('Defaults before loading file:')
defaults = parser.defaults()
for name in option_names:
    if name in defaults:
        print('  {:<15} = {!r}'.format(name, defaults[name]))

# Load the configuration file
parser.read('with-defaults.ini')

print('.Defaults after loading file:')
defaults = parser.defaults()
for name in option_names:
    if name in defaults:
        print('  {:<15} = {!r}'.format(name, defaults[name]))

# Define some local overrides
vars = {'from-vars': 'value from vars'}

# Show the values of all the options
print('.Option lookup:')
for name in option_names:
    value = parser.get('sect', name, vars=vars)
    print('  {:<15} = {!r}'.format(name, value))

# Show error messages for options that do not exist
print('.Error cases:')
try:
    print('No such option :', parser.get('sect', 'no-option'))
except configparser.NoOptionError as err:
    print(err)

try:
    print('No such section:', parser.get('no-sect', 'no-option'))
except configparser.NoSectionError as err:
    print(err)

输出显示每个选项的值的来源,并说明不同来源的默认值覆盖现有值的方式。

$ python3 configparser_defaults.py

Defaults before loading file:
  from-default    = 'value from defaults passed to init'
  from-section    = 'value from defaults passed to init'
  init-only       = 'value from defaults passed to init'
  init-and-file   = 'value from defaults passed to init'
  from-vars       = 'value from defaults passed to init'

Defaults after loading file:
  from-default    = 'value from defaults passed to init'
  from-section    = 'value from DEFAULT section'
  file-only       = 'value from DEFAULT section'
  init-only       = 'value from defaults passed to init'
  init-and-file   = 'value from DEFAULT section'
  from-vars       = 'value from DEFAULT section'

Option lookup:
  from-default    = 'value from defaults passed to init'
  from-section    = 'value from section in file'
  section-only    = 'value from section in file'
  file-only       = 'value from DEFAULT section'
  init-only       = 'value from defaults passed to init'
  init-and-file   = 'value from DEFAULT section'
  from-vars       = 'value from vars'

Error cases:
No option 'no-option' in section: 'sect'
No section: 'no-sect'

将值与插值结合

ConfigParser提供了称为 interpolation 的功能,可用于将值组合在一起。包含标准Python格式字符串的值在检索时会触发插值功能。在取回的值中命名的选项将依次替换为其值,直到不再需要替换为止。

可以重写本节前面的URL示例,以使用插值法来简化仅更改部分值的过程。例如,此配置文件将协议,主机名和端口与URL分开,作为单独的选项。

[bug_tracker]
protocol = http
server = localhost
port = 8080
url = %(protocol)s://%(server)s:%(port)s/bugs/
username = dhellmann
password = SECRET

默认情况下,每次调用get()时都会执行插值。在raw参数中传递真实值以检索原始值,而无需插值。

configparser_interpolation.py

from configparser import ConfigParser

parser = ConfigParser()
parser.read('interpolation.ini')

print('Original value       :', parser.get('bug_tracker', 'url'))

parser.set('bug_tracker', 'port', '9090')
print('Altered port value   :', parser.get('bug_tracker', 'url'))

print('Without interpolation:', parser.get('bug_tracker', 'url',
                                           raw=True))

由于该值是由get()计算的,因此更改url值所使用的设置之一将更改返回值。

$ python3 configparser_interpolation.py

Original value       : http://localhost:8080/bugs/
Altered port value   : http://localhost:9090/bugs/
Without interpolation: %(protocol)s://%(server)s:%(port)s/bugs/

使用默认值

插值的值不必与原始选项显示在同一部分。可以将默认值与替代值混合。

[DEFAULT]
url = %(protocol)s://%(server)s:%(port)s/bugs/
protocol = http
server = bugs.example.com
port = 80

[bug_tracker]
server = localhost
port = 8080
username = dhellmann
password = SECRET

使用此配置,url的值来自DEFAULT部分,并且替换通过查看bug_tracker开始并回退到DEFAULT 找不到件。

configparser_interpolation_defaults.py

from configparser import ConfigParser

parser = ConfigParser()
parser.read('interpolation_defaults.ini')

print('URL:', parser.get('bug_tracker', 'url'))

hostnameport值来自bug_tracker部分,但是 protocol 来自DEFAULT

$ python3 configparser_interpolation_defaults.py

URL: http://localhost:8080/bugs/

替代错误

MAX_INTERPOLATION_DEPTH步骤之后,替换将停止,以避免由于递归引用引起的问题。

configparser_interpolation_recursion.py

import configparser

parser = configparser.ConfigParser()

parser.add_section('sect')
parser.set('sect', 'opt', '%(opt)s')

try:
    print(parser.get('sect', 'opt'))
except configparser.InterpolationDepthError as err:
    print('ERROR:', err)

如果替换步骤太多,则会引发InterpolationDepthError异常。

$ python3 configparser_interpolation_recursion.py

ERROR: Recursion limit exceeded in value substitution: option 'o
pt' in section 'sect' contains an interpolation key which cannot
 be substituted in 10 steps. Raw value: '%(opt)s'

缺少值会导致InterpolationMissingOptionError异常。

configparser_interpolation_error.py

import configparser

parser = configparser.ConfigParser()

parser.add_section('bug_tracker')
parser.set('bug_tracker', 'url',
           'http://%(server)s:%(port)s/bugs')

try:
    print(parser.get('bug_tracker', 'url'))
except configparser.InterpolationMissingOptionError as err:
    print('ERROR:', err)

由于未定义server值,因此无法构造url

$ python3 configparser_interpolation_error.py

ERROR: Bad value substitution: option 'url' in section
'bug_tracker' contains an interpolation key 'server' which is
not a valid option name. Raw value:
'http://%(server)s:%(port)s/bugs'

转义特殊字符

由于开始插值指令,因此值中的文字必须转义为%%

[escape]
value = a literal %% must be escaped

读取值不需要任何特殊考虑。

configparser_escape.py

from configparser import ConfigParser
import os

filename = 'escape.ini'
config = ConfigParser()
config.read([filename])

value = config.get('escape', 'value')

print(value)

读取该值后,%%自动转换为

$ python3 configparser_escape.py

a literal % must be escaped

扩展插值

ConfigParser支持备用插值实现,将支持Interpolation定义的API的对象传递给interpolation参数。例如,使用ExtendedInterpolation而不是默认的BasicInterpolation启用使用$ {}表示变量的不同语法。

configparser_extendedinterpolation.py

from configparser import ConfigParser, ExtendedInterpolation

parser = ConfigParser(interpolation=ExtendedInterpolation())
parser.read('extended_interpolation.ini')

print('Original value       :', parser.get('bug_tracker', 'url'))

parser.set('intranet', 'port', '9090')
print('Altered port value   :', parser.get('bug_tracker', 'url'))

print('Without interpolation:', parser.get('bug_tracker', 'url',
                                           raw=True))

扩展内插支持通过在变量名前加上节名和冒号()来访问配置文件其他节中的值。

[intranet]
server = localhost
port = 8080

[bug_tracker]
url = http://${intranet:server}:${intranet:port}/bugs/
username = dhellmann
password = SECRET

引用文件其他部分中的值可以共享值的层次结构,而无需将所有默认值放在DEFAULTS部分中。

$ python3 configparser_extendedinterpolation.py

Original value       : http://localhost:8080/bugs/
Altered port value   : http://localhost:9090/bugs/
Without interpolation: http://${intranet:server}:${intranet:port
}/bugs/

禁用插值

要禁用插值,请传递None而不是Interpolation对象。
configparser_nointerpolation.py

from configparser import ConfigParser

parser = ConfigParser(interpolation=None)
parser.read('interpolation.ini')

print('Without interpolation:', parser.get('bug_tracker', 'url'))

这样可以安全地忽略插值对象可能已处理的任何语法。

$ python3 configparser_nointerpolation.py

Without interpolation: %(protocol)s://%(server)s:%(port)s/bugs/

另请参见

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
上一篇 下一篇
Summer
贡献者:1
讨论数量: 0
发起讨论 只看当前版本


暂无话题~