脚本专家 发表于 2026-9-14 12:00:00

Python百分号格式化教程:占位符、精度控制与错误排查

Python 的字符串格式化有三种常见方式:百分号 %、str.format() 和 f-string。百分号 % 是 Python 诞生时就存在的方式,虽然在新项目中通常优先用 f-string,但大量存量代码、logging 模块以及简单调试输出仍然会用到它。理解它的占位符、宽度精度控制和异常原因,能减少维护老项目时的排错成本。本文围绕百分号 % 的实际用法展开,示例基于 Python 3。

一、基本语法与执行规则

百分号格式化的形式是:模板字符串 % 值或值元组。模板中的 %s、%d 等叫占位符,末尾的 % 是运算符,右侧的值按顺序填入占位符。


name = '小明'
age = 25
text = '我叫%s,今年%d岁' % (name, age)
print(text)# 我叫小明,今年25岁


当只有一个值时可省略括号;多个值必须使用元组,否则会被解析成多个参数并报错。


print('%s' % 'hello')      # hello
print('%s %s' % ('a', 'b'))# a b
# print('%s %s' % 'a', 'b')# 错误,多个值必须加括号


常用占位符如下:
%s:字符串,自动调用 str();
%d / %i:十进制整数;
%f:浮点数,默认保留 6 位小数;
%e / %E:科学计数法,小写 e 或大写 E;
%x / %X:十六进制,小写或大写;
%o:八进制;
%c:字符,可接受整数或单个字符;
%r:调用 repr() 的字符串表示;
%%:输出一个百分号本身。

二、精度、宽度与对齐控制

浮点数常用 %.2f 控制小数位数,默认会四舍五入。宽度可以用 %10s、%10d、%10.2f 指定,默认右对齐;负号表示左对齐,如 %-10s。数字补零用 %010d 或 %010.2f。百分号格式化不支持居中对齐,居中对齐需要改用 format() 或 f-string。


pi = 3.141592653589793
print('%.2f' % pi)    # 3.14
print('%.4f' % pi)    # 3.1416
print('%f' % pi)      # 3.141593
print('%10s' % 'hello')   # '   hello'
print('%-10s' % 'hello')# 'hello   '
print('%010d' % 42)       # '0000000042'


对齐输出表格是常见场景:


students = [
    ('小明', 20, 85.5),
    ('小红', 21, 92.0),
    ('小刚', 19, 78.5),
]
print('%-8s %-6s %-8s' % ('姓名', '年龄', '成绩'))
print('-' * 24)
for name, age, score in students:
    print('%-8s %-6d %-8.1f' % (name, age, score))


三、%s 与 %r 的区别

%s 调用 str(),偏向给用户看的输出;%r 调用 repr(),偏向调试和日志。字符串使用 %r 时会带引号,空白和换行等不可见字符也会显示出来。


x = 'hello'
print('%s' % x)# hello
print('%r' % x)# 'hello'

value = ' hello \n'
print('str: [%s]' % value)   # str: [ hello ]
print('repr: [%r]' % value)# repr: [' hello \n']


四、字典传值与动态宽度

当模板很长、需要插入的字段很多时,可以用字典传值,占位符写成 %(name)s 的形式。这样不必严格依赖参数顺序,同一个值还可以重复引用。


person = {
    'name': '小明',
    'age': 25,
    'city': '北京',
    'job': '软件工程师'
}
template = '我叫%(name)s,今年%(age)d岁,来自%(city)s,职业是%(job)s。'
print(template % person)

template = '%(name)s的年龄是%(age)d,%(name)s住在%(city)s。'
print(template % person)


动态宽度可以用 *,宽度和精度从参数中取。例如 %*.*f 分别接收宽度、精度和数值。这个写法适合按列宽生成文本报告。


width = 10
precision = 3
print('%*.*f' % (width, precision, 3.14159))
# '   3.142'


也可以按列宽构建格式字符串:


def print_table(data, col_widths):
    for row in data:
      format_str = ''
      for width in col_widths:
            format_str += '%-*s '
      print(format_str % tuple(
            item for pair in zip(col_widths, row) for item in pair
      ))

data = [('小明', '北京', '工程师'), ('小红', '上海', '设计师')]
print_table(data, )


五、常见错误与排查

第一类错误是占位符和值的数量不匹配。占位符多于值会抛 TypeError: not enough arguments;值多于占位符会抛 TypeError: not all arguments converted。排查时先数清模板里有多少个需要替换的占位符,再检查右侧元组元素数量。

第二类错误是类型不匹配。%d 不接受浮点数,例如 '%d' % 95.5 会报 TypeError;可以先用 int() 转换,或者改用 %s。%f 也不接受字符串。

第三类错误是忘记转义百分号。要在结果中显示 %,模板里必须写 %%。如果写成 '完成度:%d%' % 80,会触发 ValueError。正确写法是 '完成度:%d%%' % 80。多个百分号同理,例如 '增长率为%.2f%%' % 15.678。


print('%s %s' % ('a', 'b'))       # a b
print('分数:%d' % int(95.5))      # 分数:95
print('分数:%s' % 95.5)         # 分数:95.5
print('完成度:%d%%' % 80)          # 完成度:80%
print('增长率为%.2f%%' % 15.678)    # 增长率为15.68%


只有一个值且该值本身是元组时,需要再包一层元组,否则整个元组会被当成多个参数或单个参数,具体取决于占位符数量。例如 print('%s' % (('a', 'b'),)) 会输出 ('a', 'b')。

六、和 format()、f-string 的选择

同一条输出可以用三种方式完成:


name = '小明'
age = 25
score = 95.5

s1 = '我叫%s,今年%d岁,成绩%.1f分' % (name, age, score)
s2 = '我叫{},今年{}岁,成绩{:.1f}分'.format(name, age, score)
s3 = f'我叫{name},今年{age}岁,成绩{score:.1f}分'
print(s1)
print(s2)
print(s3)


从版本看,百分号所有 Python 版本都支持,str.format() 从 Python 2.6 开始,f-string 从 Python 3.6 开始。可读性上 f-string 最好,并且支持表达式;百分号写法简单直接,但功能相对有限;str.format() 适合需要复用模板的场景。新代码优先 f-string;维护老代码时保持原有风格;简单调试可以继续使用 '%s=%r' % (name, value)。

七、哪些场景仍适合百分号

logging 模块是典型场景。logging 推荐写成 logging.info('用户 %s 登录了系统', name),这里用逗号分隔参数,而不是先做字符串拼接。这样日志系统可以延迟执行格式化,在日志级别不满足时不进行字符串运算。


import logging

logging.basicConfig(level=logging.INFO)
name = '小明'
logging.info('用户 %s 登录了系统', name)


此外,维护已经大量使用百分号的老项目时,保持一致比混用风格更清晰;在一些很短的一行调试输出中,百分号格式化也很方便。

八、实战:生成销售报告

下面的函数用 %-20s、%-10d、¥%-9.2f 等格式生成对齐的文本报告,并用 '\n'.join(report) 汇总输出。它体现了百分号格式化在纯文本报告、表格输出中的实际用法。


def generate_sales_report(month, data):
    """生成销售报告"""
    report = []
    report.append('=' * 50)
    report.append('%s月销售报告' % month)
    report.append('=' * 50)
    report.append('')
    report.append('%-20s %-10s %-10s' % ('商品名称', '销量', '销售额'))

    total_quantity = 0
    total_revenue = 0.0

    for item_name, quantity, price in data:
      revenue = quantity * price
      report.append('%-20s %-10d ¥%-9.2f' % (item_name, quantity, revenue))
      total_quantity += quantity
      total_revenue += revenue

    report.append('-' * 50)
    report.append('%-20s %-10d ¥%-9.2f' % ('合计', total_quantity, total_revenue))
    report.append('=' * 50)
    return '\n'.join(report)

data = [
    ('Python编程书', 150, 59.9),
    ('机械键盘', 80, 299.0),
    ('显示器支架', 45, 159.0),
    ('USB数据线', 300, 19.9),
]
print(generate_sales_report('5', data))


总结:百分号格式化的核心是“模板 + 占位符 + 值元组”。掌握 %s、%d、%f、%.2f、%10s、%-10s、%% 和 %(name)s 后,就能读懂大多数老代码。遇到 TypeError 或 ValueError 时,优先检查值数量、值类型和百分号转义。新项目可优先 f-string,但日志、老项目维护和简单调试中,百分号格式化仍有明确位置。

热心网友5 发表于 2026-9-14 19:00:00

Re: Python百分号格式化教程:占位符、精度控制与错误排查

这篇整理得挺实用。百分号格式化虽然老,但老项目、logging 和临时调试输出里确实绕不开。占位符数量不匹配、%d 传浮点数、忘记把百分号写成 %% 这几个坑,排查那节说得很清楚。宽度精度和对齐的例子也直观,%-8s 这种做简单表格输出很方便。字典传值和动态宽度平时用得少,看完例子清楚多了。感谢分享,收藏了。

热心网友3 发表于 2026-9-14 19:10:00

Re: Python百分号格式化教程:占位符、精度控制与错误排查

楼主整理得挺全面,百分号格式化确实属于那种平时不主动用、但看老代码和 logging 时绕不开的东西。占位符列表、宽度精度、%s 和 %r 的区别都讲得很清楚,尤其是用 %r 看空白和换行这个点,排查字符串问题时很实用。字典传值和动态宽度 %*.*f 也算进阶技巧了,做文本报表能省不少拼接。常见错误部分总结得也到位,数量不匹配、类型不匹配、%% 转义这三个基本都踩过。最后好像停在 prin 那里,是不是没贴完?期待补上。

热心网友2 发表于 2026-9-14 19:20:00

Re: Python百分号格式化教程:占位符、精度控制与错误排查

写得很系统,特别是 %s 和 %r 那段,用 ' hello \n' 做对比很直观,以前只知道一个带引号一个不带,没细想过是为了把不可见字符暴露出来。我们组维护的旧系统里 logging 基本都是 %s 风格,看完第二、四节再回去看那些模板就顺眼多了,动态宽度用 * 那招之前完全没意识到,按列宽生成报告时应该能少写不少拼接。 也分享一个自己踩过的坑:数据库字段有时候返回 Decimal 或者已经是浮点,直接套 %d 就抛 TypeError,后来统一改成 %s 或者先 int() 才稳。还有 %% 转义那个,模板和参数分开定义时特别容易漏,日志里少个百分号倒还好,要是往报表或者接口返回里塞,格式就悄悄错了,排查起来反而费劲。 顺便问一下,字典传值那节,如果模板里写了 %(name)s 但传进去的字典少这个 key,抛的是 KeyError 吧?我记得和数量不匹配那种 TypeError 不是一回事,想确认下思路对不对。 另外提一句,帖子最后好像被截断了,那个 prin 后面是不是还有内容没贴完,比如错误示例或者小结?
页: [1]
查看完整版本: Python百分号格式化教程:占位符、精度控制与错误排查