查看: 132|回复: 0

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

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

一、基本语法与执行规则

百分号格式化的形式是:模板字符串 % 值或值元组。模板中的 %s、%d 等叫占位符,末尾的 % 是运算符,右侧的值按顺序填入占位符。
  1. name = '小明'
  2. age = 25
  3. text = '我叫%s,今年%d岁' % (name, age)
  4. print(text)  # 我叫小明,今年25岁
复制代码

当只有一个值时可省略括号;多个值必须使用元组,否则会被解析成多个参数并报错。
  1. print('%s' % 'hello')        # hello
  2. print('%s %s' % ('a', 'b'))  # a b
  3. # 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。
  1. pi = 3.141592653589793
  2. print('%.2f' % pi)    # 3.14
  3. print('%.4f' % pi)    # 3.1416
  4. print('%f' % pi)      # 3.141593
  5. print('%10s' % 'hello')   # '     hello'
  6. print('%-10s' % 'hello')  # 'hello     '
  7. print('%010d' % 42)       # '0000000042'
复制代码

对齐输出表格是常见场景:
  1. students = [
  2.     ('小明', 20, 85.5),
  3.     ('小红', 21, 92.0),
  4.     ('小刚', 19, 78.5),
  5. ]
  6. print('%-8s %-6s %-8s' % ('姓名', '年龄', '成绩'))
  7. print('-' * 24)
  8. for name, age, score in students:
  9.     print('%-8s %-6d %-8.1f' % (name, age, score))
复制代码

三、%s 与 %r 的区别

%s 调用 str(),偏向给用户看的输出;%r 调用 repr(),偏向调试和日志。字符串使用 %r 时会带引号,空白和换行等不可见字符也会显示出来。
  1. x = 'hello'
  2. print('%s' % x)  # hello
  3. print('%r' % x)  # 'hello'
  4. value = ' hello \n'
  5. print('str: [%s]' % value)   # str: [ hello ]
  6. print('repr: [%r]' % value)  # repr: [' hello \n']
复制代码

四、字典传值与动态宽度

当模板很长、需要插入的字段很多时,可以用字典传值,占位符写成 %(name)s 的形式。这样不必严格依赖参数顺序,同一个值还可以重复引用。
  1. person = {
  2.     'name': '小明',
  3.     'age': 25,
  4.     'city': '北京',
  5.     'job': '软件工程师'
  6. }
  7. template = '我叫%(name)s,今年%(age)d岁,来自%(city)s,职业是%(job)s。'
  8. print(template % person)
  9. template = '%(name)s的年龄是%(age)d,%(name)s住在%(city)s。'
  10. print(template % person)
复制代码

动态宽度可以用 *,宽度和精度从参数中取。例如 %*.*f 分别接收宽度、精度和数值。这个写法适合按列宽生成文本报告。
  1. width = 10
  2. precision = 3
  3. print('%*.*f' % (width, precision, 3.14159))
  4. # '     3.142'
复制代码

也可以按列宽构建格式字符串:
  1. def print_table(data, col_widths):
  2.     for row in data:
  3.         format_str = ''
  4.         for width in col_widths:
  5.             format_str += '%-*s '
  6.         print(format_str % tuple(
  7.             item for pair in zip(col_widths, row) for item in pair
  8.         ))
  9. data = [('小明', '北京', '工程师'), ('小红', '上海', '设计师')]
  10. 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。
  1. print('%s %s' % ('a', 'b'))       # a b
  2. print('分数:%d' % int(95.5))      # 分数:95
  3. print('分数:%s' % 95.5)           # 分数:95.5
  4. print('完成度:%d%%' % 80)          # 完成度:80%
  5. print('增长率为%.2f%%' % 15.678)    # 增长率为15.68%
复制代码

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

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

同一条输出可以用三种方式完成:
  1. name = '小明'
  2. age = 25
  3. score = 95.5
  4. s1 = '我叫%s,今年%d岁,成绩%.1f分' % (name, age, score)
  5. s2 = '我叫{},今年{}岁,成绩{:.1f}分'.format(name, age, score)
  6. s3 = f'我叫{name},今年{age}岁,成绩{score:.1f}分'
  7. print(s1)
  8. print(s2)
  9. 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),这里用逗号分隔参数,而不是先做字符串拼接。这样日志系统可以延迟执行格式化,在日志级别不满足时不进行字符串运算。
  1. import logging
  2. logging.basicConfig(level=logging.INFO)
  3. name = '小明'
  4. logging.info('用户 %s 登录了系统', name)
复制代码

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

八、实战:生成销售报告

下面的函数用 %-20s、%-10d、¥%-9.2f 等格式生成对齐的文本报告,并用 '\n'.join(report) 汇总输出。它体现了百分号格式化在纯文本报告、表格输出中的实际用法。
  1. def generate_sales_report(month, data):
  2.     """生成销售报告"""
  3.     report = []
  4.     report.append('=' * 50)
  5.     report.append('%s月销售报告' % month)
  6.     report.append('=' * 50)
  7.     report.append('')
  8.     report.append('%-20s %-10s %-10s' % ('商品名称', '销量', '销售额'))
  9.     total_quantity = 0
  10.     total_revenue = 0.0
  11.     for item_name, quantity, price in data:
  12.         revenue = quantity * price
  13.         report.append('%-20s %-10d ¥%-9.2f' % (item_name, quantity, revenue))
  14.         total_quantity += quantity
  15.         total_revenue += revenue
  16.     report.append('-' * 50)
  17.     report.append('%-20s %-10d ¥%-9.2f' % ('合计', total_quantity, total_revenue))
  18.     report.append('=' * 50)
  19.     return '\n'.join(report)
  20. data = [
  21.     ('Python编程书', 150, 59.9),
  22.     ('机械键盘', 80, 299.0),
  23.     ('显示器支架', 45, 159.0),
  24.     ('USB数据线', 300, 19.9),
  25. ]
  26. print(generate_sales_report('5', data))
复制代码

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

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-9-14 13:11 , Processed in 0.020514 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部