ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python %格式化完全指南:从%s到%d,彻底搞懂占位符

Python %格式化完全指南:从%s到%d,彻底搞懂占位符 写了好几年Python回头看到%这个字符第一反应还是条件取模第二反应才是字符串格式化。很多新手第一次接触%占位符是在教科书上看到print(姓名%s年龄%d % (name, age))这种写法然后一脸懵这个%s到底是什么为什么不能直接拼字符串其实%格式化是Python从C语言继承过来的老手艺也是Python 3里最早的一批字符串格式化方案。虽然现在f-string和str.format()成了主流但老项目、日志模块、数据库驱动、甚至很多面试题里%仍然频繁出现。这篇博文就把%系列占位符彻底讲透从最常用的%s、%d、%f到进阶的宽度、对齐、补零、进制转换、字典映射再到实际开发中容易踩的坑一次说清楚。1. 先搞明白%格式化到底是什么为什么现在还要学1.1 %格式化的本质一个带插槽的模板我习惯把%格式化理解成填空模板。你预先写一个字符串在需要插入数据的地方放一个占位符比如%s然后用%操作符把真实数据填进去。这个过程中字符串是模板数据是填充物%就是连接两者的桥梁。举例name 张三 age 25 print(我是%s今年%d岁 % (name, age)) # 输出我是张三今年25岁注意这里有个关键细节如果模板里有多个占位符后面的数据必须用括号包成一个元组(name, age)并且数量要一一对应。如果只插入一个变量可以不加括号直接写我的名字是%s % name。从底层看%格式化做的事情是Python解析模板字符串遇到%开头的内容就识别占位符类型然后按照类型规则把对应的数据转换成字符串最终拼出完整结果。它本质上是一个类型感知的字符串拼接工具比用手动拼接更安全、更规范还自带类型转换能力。1.2 有f-string了掌握%还有必要吗这个问题的答案从项目实战角度看是很有必要。原因有三第一存量代码太多。你随便打开一个2018年以前开始的Python项目或者是维护多年的内部系统大概率能看到大量%格式化的代码。不懂它你连老代码都读不利索。尤其是日志模块logging它的%占位符风格是官方推荐用法在很多框架里根深蒂固。第二格式化的核心思想相通。%s对应的是转成字符串{}格式里的{}配合str()f-string里的{变量}本质都是同一个逻辑把数据变成人类可读的文本。学会了%再看另外两种写法会发现它们是一脉相承的只是语法糖不同。第三某些场景下%依然方便。比如格式化一个字典里的多个字段%支持%(key)s这种按键取值的方式写起来比format还直观。再比如和C语言语法对照学习你会发现这是理解Python底层设计的一把钥匙。提示Python官方文档对%格式化的术语叫printf-style String Formatting中文常翻译为printf风格字符串格式化。它的设计思想继承自C语言的printf函数这也是为什么语法风格和C语言高度一致。2. 四大核心占位符%s、%d、%f、%x全拆解2.1 %s万能字符串占位符%s是使用频率最高的占位符没有之一。它的含义是把数据转成字符串后插入支持任何类型——整数、浮点数、列表、字典、对象都可以。转换规则就是调用str()函数所以你不能塞进去一个对象就报错它总能给你一个能看的字符串形式。print(%s % 123) # 123 print(%s % 3.14) # 3.14 print(%s % [1, 2, 3]) # [1, 2, 3] print(%s % {a: 1}) # {a: 1}如果对象自己定义了__str__()方法%s会调用它这是定制输出格式的好入口。调试代码时我经常把一个复杂对象直接丢给%s快速看到对象内容比手动拼字段省事得多。%s还有个近亲%r区别在于%r调用的是repr()而不是str()。两者的差别是str()注重可读性repr()注重精确性。看一个例子s hello\nworld print(%s % s) # hello # world print(%r % s) # hello\nworld%r会把字符串带引号、转义符原样展示这在调试时特别好用——你能一眼看出字符串里到底有哪些隐藏字符。2.2 %d整数的严谨派%d专门格式化整数。它的作用是把数据转换成带符号十进制整数并且可以结合宽度、补零等修饰符控制输出格式。如果传入的不是整数Python会尝试调用__int__()转换但浮点数直接用%d会截断小数部分不是四舍五入是直接丢。print(%d % 3.99) # 3注意是截断不是四舍五入 print(%d % -7) # -7 print(%d % 5) # 报错 TypeError字符串不行这里有个新手特别容易踩的坑字符串数字5不能直接塞给%d必须先int(5)。但反过来5塞给%s却是安全的因为%s会自己转。这也是很多人误以为%d没用的原因——其实%d的价值在于格式控制比如补零和宽度后面会详细讲。%i和%d在Python里完全等价可以简单理解为%i是C语言里的另一个别名但我建议统一用%d因为Python官方文档也是以%d为主。写代码统一风格维护成本最低。2.3 %f浮点数的精度管家%f用于格式化浮点数默认保留6位小数。它的格式修饰符非常实用%.2f表示保留两位小数%.3f保留三位以此类推。这里有个重要细节浮点格式化会四舍五入但受限于二进制浮点表示某些数字的四舍五入结果可能和数学直觉不完全一致。print(%f % 3.1415926) # 3.141593默认保留6位 print(%.2f % 3.14159) # 3.14 print(%.0f % 2.5) # 2注意这里不是3 print(%.0f % 3.5) # 4看到没有2.5保留0位小数输出竟然是2不是3。这不是Bug是浮点数的二进制表示导致的银行家舍入行为2.5在内存里其实是2.4999999999999998所以舍入后还是2。这种坑在做财务计算、报表生成时要特别小心如果对精度有严苛要求优先用decimal.Decimal而不是浮点数。%e和%E是浮点数的科学计数法格式%e输出小写字母e%E输出大写字母Eprint(%e % 12345.678) # 1.234568e04 print(%E % 12345.678) # 1.234568E04 print(%.2e % 12345.678) # 1.23e04科学计数法适合展示极大或极小的数值比如物理常数、天文数据。%g则更智能它会自动在普通十进制和科学计数法之间选择更短的那个类似C语言的printf默认行为。老练的程序员遇到不知道用户想看到什么格式的场景就用%g懒人处理。2.4 %x及相关进制转换其实可以这么顺手%x把整数转成十六进制字符串%o转成八进制而且支持修饰符。在用Python写底层通信协议、解析二进制数据时这个特别有用。想象一下你从一个二进制文件里读了一个字节值可能是255你想看它的十六进制形式直接%x % 255就输出了ff不用自己写转换逻辑。print(%x % 255) # ff print(%X % 255) # FF大写十六进制 print(%o % 8) # 10 print(%#x % 255) # 0xff带前缀 print(%#o % 8) # 0o10带前缀%#x里的#是备选格式标志加上后会在十六进制前加0x在八进制前加0o和Python源码里字面量的写法一致。这在生成C语言风格的常量表时很实用。%b是二进制不存在的。Python的%系列没有直接的占位符表示二进制想格式化二进制只能用bin()函数配合%s或者format()里的{:b}。这个细节很多人会记混特此提醒。3. 格式修饰符让输出更规整的高级玩法3.1 宽度与对齐让表格对齐不再靠手工补空格%10s表示这个占位符至少占10个字符宽度如果内容不足10个字符默认在左边补空格也就是右对齐。如果想左对齐在宽度前加负号%-10s。这一招在打印对齐的报表、命令行帮助信息时特别有用。name_list [Tom, Jerry, Spike] for name in name_list: print(%-10s| %s % (name, 完成)) # 输出 # Tom | 完成 # Jerry | 完成 # Spike | 完成宽度修饰符对字符串、整数、浮点数都适用。浮点数还有更讲究的写法%8.2f表示总宽度为8包含小数点保留2位小数。如果数值实际宽度不足8前面补空格如果超过8则不会被截断按实际宽度输出——这是很多人的认知盲区以为宽度是硬截断其实不是。print(%8.2f % 3.14) # 3.14总宽8右对齐 print(%8.2f % 12345.678) # 12345.68超过宽度不会截断3.2 填充与符号0、、空格的前缀魔法在宽度前面加0表示用0填充而不是空格。这在生成定长编号、日期字符串时极其实用。%05d表示总宽度5不足补0print(%05d % 42) # 00042 print(%05.2f % 3.14) # 03.14 print(%d % 42) # 42正数强制显示正号 print(%d % -42) # -42 print(% d % 42) # 42正数前补空格 print(% d % -42) # -42标志让正数也带正号这在财务报表里很常见用来明确区分收支方向。空格标志则是在正数前留一个空位让正负数的-号和空格对齐视觉上更整齐。还有一个.0精度标志可以和%d配合%.3d表示整数部分至少显示3位不足补0效果类似%03d。3.3 动态宽度用*号让参数自己决定宽度有些场景下宽度不是一个固定值而是由程序运行时的数据决定的。比如你从配置文件读到一个对齐宽度或者要动态计算表头长度。这时候可以在格式串里用*号占位宽度然后把宽度值作为参数传给元组命令格式变成%*d % (width, number)width 10 print(%*d % (width, 42)) # 42 price 3.14159 print(%*.2f % (width, price)) # 3.14*号还可以同时控制宽度和精度比如%*.*f % (10, 2, 3.14159)表示宽度10、精度2。这种写法在C语言里很常见Python也完整继承了过来。不过说实话日常开发里*用得不多因为f-string里直接嵌套变量更直观。但读老代码时遇到了要知道它是啥意思。3.4 字典映射%格式化的隐藏高级玩法这个功能很多人用了好几年Python都没发现%(name)s这种写法可以通过名字从字典里取字段不用再按位置对齐。它的核心优势是当字典字段很多时代码可读性极强而且字段顺序无关紧要。user {name: Alice, age: 30, city: Beijing} print(我是%(name)s今年%(age)d岁来自%(city)s % user) # 输出我是Alice今年30岁来自Beijing这种写法特别适合从配置字典、数据库查询结果生成文本的场景。比如从SQLite查出一条用户记录字段都在一个字典里用%(column)s直接引用免去了手动解包的麻烦。配合*还能玩出更多花样但基础用法记住就够了。4. %格式化在真实项目里的典型应用场景4.1 日志输出中的printf风格Python标准库logging模块对%格式化有特殊优待。官方推荐的日志写法就是logging.info(用户 %s 登录成功IP: %s, username, ip)注意这里参数是分开传入的不是先拼好字符串再传给日志。这个设计是有讲究的如果日志级别不满足输出条件比如INFO级日志被过滤掉logging压根不会执行格式化操作能省一次字符串拼接的开销。这在打印量极大的生产环境里是实打实的性能优化。import logging logging.basicConfig(levellogging.INFO) user zhangsan ip 192.168.1.100 logging.info(用户 %s 登录成功IP: %s, user, ip) # 输出INFO:root:用户 zhangsan 登录成功IP: 192.168.1.100如果改用f-string比如logging.info(f用户 {user} 登录成功)无论日志是否输出都会先执行一次字符串格式化这就是无谓开销。所以很多大厂规范里明确要求日志模块必须用%占位符风格不允许用f-string。这算是%在当代Python生态里最顽强的生存阵地。4.2 构建文件路径和生成定长编号在运维脚本、数据导出工具里经常要生成格式化的文件名。比如按日期生成日志文件import datetime now datetime.datetime.now() filename backup_%04d%02d%02d_%02d%02d%02d.tar.gz % (now.year, now.month, now.day, now.hour, now.minute, now.second) print(filename) # 输出backup_20250614_153022.tar.gz这里%04d保证了年份和月份的前导零文件名字段长度固定在按文件名排序时不会出现10排在2前面的字典序问题。做批量文件、工单编号、订单号这类场景%04d是控制位数的利器。再比如批量打印设备列表用%-15s对齐名称列用%8.2f对齐金额列生成的文本报表能直接贴进邮件或工单系统不用额外写对齐逻辑。4.3 数据库SQL语句的格式化注意安全边界在早期Python代码里少量参数拼接SQL时会用%格式化。比如SELECT * FROM users WHERE name %s % name。这个写法能跑但绝不在生产环境推荐因为有SQL注入风险。真正的安全做法是用数据库驱动提供的参数化查询也就是?或%s占位符交给驱动处理比如pymysql的cursor.execute(SELECT * FROM users WHERE name %s, (name,))。这里面的微妙区别在于驱动内部的%s不是Python的%格式化而是数据库驱动自己解析的占位符参数值会经过转义处理不会拼进SQL字符串。新手容易把两者搞混拿Python的%去拼SQL然后被注入攻击教做人。我见过一个真实案例某个内部管理系统把用户输入直接%s % user_input拼进SQL结果被删了整张表。安全红线不能碰字符串格式化再方便也不能用于直接拼SQL。5. 常见坑与排查心得这些错误我全踩过5.1 类型不匹配%d塞了字符串直接TypeError最常见的报错是TypeError: %d format: a number is required, not str。原因就是占位符要求数字但传入的是字符串。解决办法很简单先int()转一下或者把占位符改成%s。关键是搞清楚数据在流程里到底被转成了什么类型——调试时用type()先确认比瞎猜快得多。5.2 参数数量对不上Not enough arguments模板里有3个占位符但只传了2个参数Python会抛TypeError: not enough arguments for format string。这个错误在代码里一眼就能看出来但真正的坑在运行时如果数据源是动态拼接的元组数量可能随条件变化。比如data (1, 2) print(%d and %d and %d % data) # TypeError解决思路是统一数据结构的长度或者干脆避免在动态场景用%改用f-string或format()它们对参数数量的宽容度更高。5.3 浮点精度%.2f不等于精确的两位小数如前面所述%.2f是四舍五入到两位小数但底层的浮点表示会导致2.5变成2这种反直觉结果。如果业务要求精确舍入比如金融金额用decimal模块from decimal import Decimal, ROUND_HALF_UP value Decimal(2.5) print(value.quantize(Decimal(0), roundingROUND_HALF_UP)) # 3记住%f适合展示不适合计算。展示时可以接受微小的舍入差异但计算和存储金额级别数据千万别用浮点数裸算否则对账对不上哭都来不及。5.4 转义%%想输出百分号结果报错如果你在模板里写了一个%想原样输出比如输出完成度80%必须写成%%。因为%是转义开始符单独一个%后面没有合法类型符会直接报ValueError: unsupported format character。print(完成度%d%% % 80) # 输出完成度80%这是我见过新手犯得最多的错没有之一。每一次需要输出百分号都要条件反射地写双%%。5.5 字符串里的%被误解析模板字符串里如果包含不需要格式化的%比如网址里的%20URL编码或者SQL里的LIKE %abc%这些%会被Python当成占位符开始解析导致报错。解决方案是把这些%写成%%或者干脆把这个模板字符串拆开用变量拼接避免冲突。6. 三种格式化方式横向对比% vs format() vs f-string为了帮你做技术选型我把三种方式的关键差异列个表特性% 格式化str.format()f-stringPython版本全版本2.6 / 3.03.6可读性中等较好最好性能中等最慢最快日志模块支持官方推荐支持但需额外配置不推荐立即格式化字典映射支持%(name)s支持{name}支持变量直访复用模板可以多次%支持先定义再format不支持调试方便度一般一般最好一个很容易忽略的点f-string的性能优势在大量字符串操作时很可观。Python官方在3.12里还做了一轮f-string优化速度更快。所以新代码优先用f-string基本是社区共识。但f-string有一个限制是定义时必须立即求值无法把模板定义为字符串反复使用。而%和format()可以把模板存起来之后再用不同数据填充。比如把用户通知模板存在配置文件里用%或format动态填充f-string做不到除非配合eval但那是自找麻烦。选择建议新写的业务代码、追求可读性和性能直接f-string日志模块统一用%风格模板需要从外部加载、延迟渲染用format()或%面试和读老代码三种都得认识7. 终极大法一张速查表搞定所有占位符写法我把自己多年整理的一份%格式化速查表放这里直接抄走用写法含义示例结果%s字符串str()%s % 123→123%r字符串repr()带引号%r % a→a%d/%i有符号十进制整数%d % -5→-5%u无符号十进制整数%u % -5→4294967291%o八进制%o % 8→10%x/%X十六进制大小写%x % 255→ff%f/%F浮点数默认6位小数%f % 1.2→1.200000%e/%E科学计数法%.2e % 1234→1.23e03%g/%G自动选择%f或%e%g % 0.00001234→1.234e-05%%转义百分号%d%% % 80→80%%5d宽度5右对齐%5d % 42→ 42%-5d宽度5左对齐%-5d % 42→42 %05d宽度5补0%05d % 42→00042%d显示正负号%d % 42→42%.2f浮点数保留2位小数%.2f % 3.14159→3.14%8.2f宽度8保留2位小数%8.2f % 3.14→ 3.14%(name)s字典映射%(name)s % {name: Tom}→Tom%*d动态宽度%*d % (5, 42)→ 42%#x带前缀十六进制%#x % 255→0xff常见的组合也顺手列一下%-10s生成左对齐的定宽字符串%05d生成定长数字编号%x做协议调试时打印内存值%.3f做数据展示时保留三位小数。遇到需求先查这张表比硬记强多了。8. 实操收尾我的真实感想和下一步建议这几年做培训和代码评审发现一个有意思的现象凡是能熟练讲清楚%和f-string区别的人对Python的数据类型和对象模型理解都比较扎实。原因在于格式化看起来是个小功能但它牵涉字符串转换、类型映射、对象协议、性能权衡一堆底层概念。把%吃透其实是在帮你打通Python字符串处理的任督二脉。我的建议是别再纠结用哪种格式化最好这种问题。按场景选日志用%新代码用f-string加载外部模板用format()面试题全部熟练掌握。同时一定要动手敲一遍上面的例子把%05d、%8.2f、%(name)s这些亲手跑一遍感受一下输出差异比看十篇博文都有效。特别是%.0f对2.5输出2这个现象自己跑一遍会印象深刻得多面试被问到也不会慌。最后分享一个我自己项目里的小技巧如果有一段字符串既要做格式化又担心里面有特殊字符搞乱模板我会先在代码里用repr()打印模板看原始形态确认没有多余的%再上正式逻辑。排查格式化问题最有效的手段永远是先看模板再看数据最后看类型按这个顺序走绝大多数坑都能快速定位。
返回列表