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, [8, 6, 8])
复制代码
五、常见错误与排查
第一类错误是占位符和值的数量不匹配。占位符多于值会抛 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,但日志、老项目维护和简单调试中,百分号格式化仍有明确位置。 |