目录
一、绪论
二、古代方法 —— %
2.1 示例
2.2 速查表
三、近代方法 —— format
3.1 示例
3.2 速查表 (数字格式化)
四、现代方法 —— f-string
4.1 说明
4.2 示例
一、绪论
随着 Python 的更新,其字符串格式化功能也在不断地优化。在 Python 2.6 以前,% 操作符一枝独秀;在 Python 2.6 之后,迎来后起之秀 format 操作符;在 Python 3.6 之际,更有 f-string 操作符异军突起。字符串格式化的逐步优化,不但简化了操作,而且提升了效率,如下执行时间对比柱状图所示:
可见,远古时期的模板字符串 str.Template 过于低效,str.format 和 % 操作符在简便和效率之间有所权衡,而 f-string 则具有最佳性能。以下将逐一回顾:
二、古代方法 —— %
2.1 示例
作为最古老格式化字符串方式,% 操作符与 C 中 sprintf 函数的语法基本相同,因而具有浓浓的 C 风味,使得最终的表达式可能较为复杂和冗长。关于 % 操作符的用法一言以蔽之 —— 在目标字符串中指定位置插入待填充内容的类型符号。例如:
1 2 3 4 5 6 7 | >>> "%s is %d years old." % ('Zhang San', 18) # 直接使用 'Zhang San is 18 years old.' # ------------------------------------------------------------------------- >>> name = 'Zhang San' >>> age = 18 >>> "%s is %d years old." % (name, age) # 间接使用 'Zhang San is 18 years old.' |
2.2 速查表
常用类型符号及其说明:
| 符号 | 说明 |
| %s | 格式化字符串 |
| %c | 格式化字符及其 ASCII 码 |
| %d | 格式化整数 |
| %u | 格式化无符号整型 |
| %o | 格式化无符号八进制数 |
| %x | 格式化无符号十六进制数 |
| %X | 格式化无符号十六进制数 (大写) |
| %f | 格式化浮点数,可指定小数点后的精度 |
| %e | 格式化浮点数 (科学计数法) |
| %E | 同 %e |
| %g | %f 和 %e 的简写 |
| %G | %F 和 %E 的简写 |
| %p | 用十六进制数格式化变量的地址 |
辅助指令:
| 符号 | 说明 |
| * | 定义宽度或小数点精度 |
| - | 用于左对齐 |
| + | 正数前显示加号 '+' |
| 正数前显示空格 | |
| # | 八进制数前显示零 '0'、十六进制数前显示 '0x' 或 '0X' |
| 0 | 显示的数字前面填充'0'而不是默认的空格 |
| % | '%%' 输出一个 '%' (百分号转义) |
| (var) | 映射变量 (字典参数) |
| m.n. | m 是显示的最小总宽度,n 是小数点后的保留位数 |
三、近代方法 —— format
3.1 示例
相比于 % 操作符,str.format() 在少量降低性能的条件下,强化了字符串格式化的功能,其基本语法是接受一个格式字符串及任意一组位置与关键字参数,通过 { } 和 : 来代替 %,而且参数数量没有限制 (相对概念),参数位置可以不按顺序。例如:
1 2 3 4 5 6 7 8 | >>> "{} is {} years old.".format('Li Hua', 3) # 不指定位置索引, 默认排列 'Li Hua is 3 years old.' # ------------------------------------------------------------------------- >>> "{0} is {1} years old.".format('Li Hua', 3) # 指定位置索引 'Li Hua is 3 years old.' # ------------------------------------------------------------------------- >>> "{0} is {1}{1} years old.".format('Li Hua', 3) # 指定位置索引, 多次使用 'Li Hua is 33 years old.' |
还可以对 str.format() 设定参数:(其中使用 * 和 ** 解包的原理详见文章 单/双星号操作符)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | >>> "宿舍楼:{building}, 门牌号:{number}".format(building="鹏翔2斋", number=1234) '宿舍楼:鹏翔2斋, 门牌号:1234' # ------------------------------------------------------------------------- >>> info = {"building":"鹏翔2斋", "number":1234} >>> "宿舍楼:{building}, 门牌号:{number}".format(**info) # 使用 ** 对 dict 解包 '宿舍楼:鹏翔2斋, 门牌号:1234' # ------------------------------------------------------------------------- >>> info2 = ("鹏翔2斋", 1234) >>> "宿舍楼:{}, 门牌号:{}".format(*info) # 使用 * 对 tuple 解包 '宿舍楼:鹏翔2斋, 门牌号:1234' # ------------------------------------------------------------------------- >>> info3 = ["鹏翔2斋", 1234] >>> "宿舍楼:{0[0]}, 门牌号:{0[1]}".format(info3) # 使用 index 指定 list 元素 '宿舍楼:鹏翔2斋, 门牌号:1234' |
甚至可以向 str.format() 传入对象:
1 2 3 4 5 6 7 | >>> class Num: def __init__(self, num): self.num = num >>> six = Num(6) >>> "my number is {0.num}".format(six) # 调用成员属性作为参数 'my number is 6' |
以上实例足以说明 str.format() 的灵活与强大!
3.2 速查表 (数字格式化)
以下仅列举常见用法示例及其说明:
| 示例 | 格式 | 结果 | 说明 |
| 3.1415 | {:.2f} | 3.14 | 保留小数点后2位 |
| 2.7182 | {:+.2f} | +2.71 | 带符号保留小数点后2位 |
| -1 | {:+.2f} | -1.00 | 带符号保留小数点后2位 |
| 1.2345 | {:.0f} | 1 | 删去小数部分 |
| 8 | {:x>2d} | 08 | 整数补x (填充左边, 宽度为2) |
| 16 | {:y<4d} | 16xx | 整数补y (填充右边, 宽度为4) |
| 1000000 | {:,} | 1,000,000 | , 作千分位分隔符 |
| 1000000 | {:_} | 1_000_000 | _ 作千分位分隔符 |
| 0.48 | {:.4%} | 48.0000% | 小数转百分比 |
| 100000000 | {:.2e} | 1.00e+08 | 指数记法 |
| 16 | {:>8d} | 16 | 向右对齐 (宽度为8) |
| 16 | {:<8d} | 16 | 向左对齐 (宽度为8) |
| 16 | {:^8d} | 16 | 居中对齐 (宽度为8) |
还有进制转换:(# 符号用于切换数字显示,可获取各进制的带前缀大、小写表示)
| 示例 | 格式 | 结果 | 说明 |
| 12 | {:#b} | 0b1100 | 带前缀 二进制小写表示 |
| 12 | {:b} | 1100 | 二进制表示 |
| 12 | {:o} | 14 | 八进制表示 |
| 12 | {:d} | 12 | 十进制表示 |
| 12 | {:x} | c | 十六进制表示 |
| 12 | {:#X} | 0XC | 带前缀 十六进制大写表示 |
事实上,str.format() 的用法远比上述示例复杂多样,详见官方文档:格式字符串语法。
四、现代方法 —— f-string
4.1 说明
相较于前两种方法,Python 3.6 中的 f-string (格式化字符串常量 - formatted string literals),应为最佳形式 —— 简洁、易读,不仅有助于简化十分繁琐的写法,而且具有最佳效率,非常建议使用。与具有恒定值的其它字符串常量不同,格式化字符串实际上是运行时运算求值的表达式。
f-string 的语法结构为:
1 | f ' <text> { <expression> <optional !s, !r, or !a> <optional : format specifier> } <text> ... ' |
其中,f (或 F) 为目标字符串前缀;
4.2 示例
其常用方法是在目标字符串前加上字母 f 或 F,然后在目标字符串中插入占位符(直接在 { } 中加入变量)。例如:
1 2 3 4 | >>> name = 'Bob' >>> age = 24 >>> f"{name} is {age} years old." # 格式字符串前缀 f 或 F, 中间插入占位符 {variable} 'Bob is 24 years old.' |
其实,花括号 { } 中可以灵活地填入各种表达式,甚至可以调用函数与类成员等,而不仅仅局限于变量,例如:
1 2 3 4 5 6 7 8 9 | >>> f"The result of the function is {2 * 3 + 4}" # 算术表达式 内联运算 'The result of the function is 9.43656365691809' # ---------------------------------------------------------------------------- >>> f'My weapen is {weapen.upper()}' # 函数/方法调用 'My weapen is AK47' # ---------------------------------------------------------------------------- >>> import math >>> f"The result of the function is {2 * math.e + 4}" # 类成员(属性/方法)调用 'The result of the function is 9.43656365691809' |
当然,除了内置函数和自定义普通函数,lambda 表达式 (匿名函数) 也可以填入 { },但为避免 lambda 表达式的分号 : 带来的歧义,应将 lambda 表达式置于圆括号 ( ) 中,例如:
1 2 3 4 5 | >>> f'The test result is {lambda x: x + 1 (99)}' # 无法解析 End-of-File SyntaxError: unexpected EOF while parsing # ---------------------------------------------------------------------------- >>> f'The test result is {(lambda x: x + 1)(99)}' # 格式:{(lambda表达式)(lambda表达式参数)} 'The test result is 100' |
为避免引号冲突,可以灵活使用 单引号 ' 、双引号 '' 、单引号文本字符串 ' ' '、双引号文本字符串 '' '' '' 来解决,只需保证目标字符串外定界符的引号 与 目标字符串中占位符内的引号不同。例如:
1 2 3 4 5 6 7 8 9 10 | >>> f"We are {"CT"}" # 内外均使用了双引号 "" SyntaxError: invalid syntax # ---------------------------------------------------------------------------- >>> f"We are {'CT'}" # 内用单引号 '' 外用双引号 "" 'We are CT' # ---------------------------------------------------------------------------- >>> f'''We are {'CT'} and you are {"T"} ''' # 内用单双 外用三 'We are CT and you are T ' # 不论怎么搭配, 只要内外不冲突即可 |
但注意,占位符 { } 内不能使用转义符号 \ 对引号转义 (因为不允许出现 \,除非用变量间接传递),而上下文可以:
1 2 3 4 5 6 7 8 9 | >>> f'I think {you\'re powerful}' # {} 内不可使用转义符号 \ SyntaxError: f-string expression part cannot include a backslash # ---------------------------------------------------------------------------- >>> f'You\'re powerful' # {} 外则可同正常字符串一样使用转义符号 \ "You're powerful" # ---------------------------------------------------------------------------- >>> words = "you\'re powerful" # 除非用变量传递 >>> f'I think {words}' "I think you're powerful" |
此外,更多技术细节详见官方文档。由于 f-string 是基于 str.format 发展而来的, str.format 所用符号 同样适用于 f-string (所以可以使用 3.2 节的速查表)。
参考文献:
https://docs.python.org/zh-cn/3.6/library/string.html#formatstrings
https://www.runoob.com/python/att-string-format.html
https://www.runoob.com/python/python-strings.html
https://www.jianshu.com/p/72cc021cf30a
https://zhuanlan.zhihu.com/p/79826445
https://docs.python.org/3/reference/lexical_analysis.html#f-strings