登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  python教程

Python configparser 怎样转义值里的百分号

来源:17golang原创

时间:2026-10-15 04:51:06 393浏览 收藏

我在把一个带折扣比例的 INI 配置交给 configparser 读取时,最容易误判的是:百分号看起来只是普通字符,但默认解析器会先尝试把它当成插值语法。结果就是配置里的 80% 可能在读取阶段报错,而不是原样返回。

官方文档:https://docs.python.org/3/library/configparser.html

处理方法很明确:如果值里是一个字面百分号,在 BasicInterpolation 模式下写成 %%;如果确实要读取插值占位符原文,用单次 get(..., raw=True);如果整份配置都不需要插值,再初始化时使用 interpolation=None。这三种写法解决的是不同问题,不能只记住“把百分号加倍”这一条。

先区分字面百分号和插值占位符

ConfigParser 默认启用 BasicInterpolation。它允许一个配置值引用同一节或 DEFAULT 中的另一个值,形式是 %(name)s。因此百分号在这里不完全是普通文本:它既可能是插值表达式的开头,也可能是业务值中的字面字符。

例如,%(base_dir)s/cache 表示先取 base_dir 再拼接路径;而折扣、进度、税率这类值里的 % 通常只是要原样交给业务代码。两种意图混在同一个解析器里,正是报错的来源。

ConfigParser 与 BasicInterpolation 连接 80%%、百分号插值占位符、配置值和 get 方法的浅色工程蓝图说明图
图1:BasicInterpolation 的静态关系说明图;%% 表示字面百分号,%(name)s 表示配置引用。

字面百分号为什么要写成 %%

在 BasicInterpolation 中,连续两个百分号代表一个最终的字面百分号。配置文件里不要直接写 80%,而要写 80%%;读取时,get() 会返回字符串 80%。

[pricing]
# 使用两个百分号,读取后得到一个字面百分号。
discount = 80%%

[paths]
# 这里的百分号是插值标记,引用 DEFAULT 中的 base_dir。
cache = %(base_dir)s/cache

[DEFAULT]
# 插值引用的源值。
base_dir = /srv/app
import configparser

# 默认解析器启用 BasicInterpolation,会把 %% 还原为一个 %。
parser = configparser.ConfigParser()
parser.read_string("""
[DEFAULT]
# 提供给 %(base_dir)s 使用的基础目录。
base_dir = /srv/app

[pricing]
# 配置文本中的 %% 表示一个字面百分号。
discount = 80%%

[paths]
# 这个占位符会引用 DEFAULT 的 base_dir。
cache = %(base_dir)s/cache
""")

# 读取后的值已经完成插值,但 discount 仍是普通字符串。
print(parser.get("pricing", "discount"))
print(parser.get("paths", "cache"))

输出分别是 80% 和 /srv/app/cache。这里要注意,%% 只是在解析阶段表示一个字面百分号;它不是 Python 字符串格式化,也不是正则表达式转义。

用 Python 写入配置时也遵循同一规则

如果配置不是从文件读入,而是由程序通过映射接口或 set() 写入,仍然要按照当前解析器的插值语义组织值。将字面百分号存成 %%,读取时才能稳定得到一个 %。

import configparser
from io import StringIO

# 这个解析器保留默认的 BasicInterpolation 行为。
parser = configparser.ConfigParser()

# 写入字面百分号时使用 %%,不要直接把单个 % 当成普通文本。
parser["service"] = {"quota": "80%%"}

# 将配置写入内存文本,便于后续交给同样的解析器读取。
buffer = StringIO()
parser.write(buffer)

# 重新读取,模拟文件落盘后再次加载的过程。
loaded = configparser.ConfigParser()
loaded.read_string(buffer.getvalue())

# 读取阶段把 %% 还原成一个字面百分号。
print(loaded.get("service", "quota"))

这个规则的好处是“写入”和“读取”保持同一套约定。如果一个组件直接生成 INI,另一个组件却用原始文本的想法去拼值,就容易把 %% 当成最终内容,进而在业务层得到两个百分号。

只想拿原文时使用 raw=True

有些场景不是想把插值关闭,而是要检查配置原文、展示模板,或者把下一阶段才处理的占位符交给别的系统。这时不要修改整个解析器,只在读取动作上加 raw=True。

import configparser

# 读取包含插值占位符的配置文本。
parser = configparser.ConfigParser()
parser.read_string("""
[DEFAULT]
# 这个值会被 BasicInterpolation 作为源值使用。
root = /srv/app

[template]
# 这里保存的是一个需要暂缓处理的占位符。
path = %(root)s/cache
""")

# 默认读取会展开 %(root)s。
expanded = parser.get("template", "path")

# raw=True 只影响本次读取,返回尚未展开的原文。
raw_value = parser.get("template", "path", raw=True)

print(expanded)
print(raw_value)

raw=True 的作用域是本次取值,不会把同一个解析器改成“以后都不插值”。如果只是在日志、模板检查或迁移脚本中偶尔需要原文,它比全局关闭插值更安全。

什么时候应该关闭 interpolation

如果整份配置都把百分号、括号和其他格式符号当作普通文本,不需要 %(name)s 这种引用关系,可以在初始化时关闭插值:

import configparser

# 关闭所有插值,让配置值按原文返回。
parser = configparser.ConfigParser(interpolation=None)
parser.read_string("""
[metrics]
# 单个百分号不再被当成插值语法。
rate = 80%

[template]
# 占位符也会作为普通文本保留。
path = %(root)s/cache
""")

# 两个值都不做插值展开。
print(parser.get("metrics", "rate"))
print(parser.get("template", "path"))

也可以使用 RawConfigParser,它的默认插值行为就是关闭的。不过在团队代码里,显式写出 ConfigParser(interpolation=None) 往往更容易让读者看出这是一个有意的解析策略,而不是忘了配置。

INI 文本经过 ConfigParser 后由 get、raw=True、interpolation=None 和 RawConfigParser 连接到原始百分号的工程蓝图说明图
图2:读取策略的静态关系说明图;局部原文读取与全局关闭插值承担不同职责。

单个百分号报错时怎样定位

如果配置中写了单个 %,读取时可能出现 InterpolationSyntaxError;如果写成了一个插值引用但对应键不存在,则通常会出现 InterpolationMissingOptionError。排查时先问三个问题:这个百分号是不是业务字面量、当前读取是否需要展开、值是由文件还是代码生成的。

import configparser

# 保留默认插值,用异常类型区分语法问题和缺失引用。
parser = configparser.ConfigParser()
parser.read_string("""
[service]
# 这里故意使用字面百分号的错误写法,读取时会触发插值解析错误。
rate = 80%
""")

try:
    # 读取动作才会真正触发插值处理。
    print(parser.get("service", "rate"))
except configparser.InterpolationError as exc:
    # 把节名、键名和解析异常一起记录,便于定位来源配置。
    print(type(exc).__name__, exc.section, exc.option)

如果该字段就是字面值,修正配置为 80%%;如果需要检查原文,临时使用 raw=True;如果所有字段都不需要插值,才把解析器改成 interpolation=None。不要为了绕过一个字段的问题,把全局解析规则随意改掉。

三种写法怎么选

需求推荐写法读取结果
配置值需要一个字面百分号80%%80%
只在一次读取中保留插值原文get(section, option, raw=True)返回未展开文本
整份配置都不使用插值ConfigParser(interpolation=None)所有值按原文读取

我实际更倾向于把选择写在创建解析器或读取函数附近,而不是只在注释里说“这里有百分号”。这样后续维护者能从代码直接看到:该值是要参与插值、暂时保留模板,还是完全按原文处理。

常见问题

为什么 80% 不能直接读取?

因为默认 ConfigParser 使用 BasicInterpolation,百分号可能被当成插值语法开头。字面百分号应写成 80%%。

raw=True 会永久关闭插值吗?

不会。它只影响当前这次 get() 或相关读取调用,后续普通读取仍会按照解析器默认规则展开。

什么时候用 RawConfigParser?

当配置格式本身明确不需要插值时可以使用。若项目更强调可读性和显式意图,ConfigParser(interpolation=None) 通常更容易被维护者理解。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>