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 再拼接路径;而折扣、进度、税率这类值里的 % 通常只是要原样交给业务代码。两种意图混在同一个解析器里,正是报错的来源。

字面百分号为什么要写成 %%
在 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) 往往更容易让读者看出这是一个有意的解析策略,而不是忘了配置。

单个百分号报错时怎样定位
如果配置中写了单个 %,读取时可能出现 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) 通常更容易被维护者理解。
-
143 收藏
-
114 收藏
-
165 收藏
-
478 收藏
-
417 收藏
-
298 收藏
-
102 收藏
-
306 收藏
-
204 收藏
-
319 收藏
-
144 收藏
-
417 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习