Python 四种字符串格式化方式

Python 四种字符串格式化方式

格式化(formatting)是指把数据填写到预先定义的文本模板里面,形成一条用户可读的消息,并把这条消息保存成字符串的过程。

% 格式化

Python 里面最常用的字符串格式化方式是采用 % 格式化操作符。

这个操作符左边的文本模板叫作格式字符串(format string),我们可以在操作符右边写上某个值或者由多个值所构成的元组(tuple),用来替换格式字符串里的相关符号。

例如,下面这段代码通过 % 操作符把难以阅读的二进制和十六进制数值,显示成十进制的形式。

a = 0b10111011
b = 0xc5f
print('Binary is %d, hex is %d' % (a, b))
# >>>
# Binary is 187, hex is 3167

格式字符串里面可以出现 %d 这样的格式说明符,这些说明符的意思是,% 右边的对应数值会以这样的格式来替换这一部分内容。格式说明符的写法来自 C 语言的 printf 函数,所以,常见的 printf 选项都可以当成 Python 的格式说明符来用,例如 %s、%x、%f 等,此外还可以控制小数点的位值,并指定填充与对齐方式。

但是,C 风格的格式字符串,在 Python 里有四个缺点。

第一个缺点是,如果 % 右侧那个元组里面的值在类型或顺序上有变化,那么程序可能会因为转换类型时发生不兼容问题而出现错误。例如一个简单的例子:

key = 'my_var'
value = 1.234
formatted = '%-10s = %.2f' % (key, value)
print(formatted)
# >>>
# my_var     = 1.23

但如果把 key 跟 value 互换位置,那么程序就会在运行时出现异常。

reordered_tuple = '%-10s = %.2f' % (value, key)
# >>>
# TypeError: must be real number, not str

如果 % 右侧的写法不变,但左侧那个格式字符串里面的两个说明符对调了顺序,那么程序同样会发生这个错误。

reordered_string = '%.2f = %-10s' % (key, value)
# >>>
# TypeError: must be real number, not str

要想避免这种问题,必须经常检查 % 操作符左右两侧的写法是否相互兼容。

第二个缺点是,在填充模板之前,经常要先对准备填写进去的这个值稍微做一些处理,但这样一来,整个表达式可能就会写得很长,让人觉得比较混乱。

下面这段代码用来罗列厨房里的各种食材,现在的这种写法并没有对填入格式字符串里面的那三个值(也就是食材的编号 i、食材的名称 item,以及食材的数量 count)预先做出调整。

pantry = [('avocados', 1.25),('bananas', 2.5),('cherries', 15),
]
for i, (item, count) in enumerate(pantry):print('#%d: %-10s = %.2f' % (i, item, count))
# >>>
# #0: avocados   = 1.25
# #1: bananas    = 2.50
# #2: cherries   = 15.00

如果想让打印出来的信息更好懂,那可能得把这几个值稍微调整一下,但是调整之后,% 操作符右侧的那个三元组就特别长,所以需要多行拆分才能写得下,这会影响程序的可读性。

for i, (item, count) in enumerate(pantry):print('#%d: %-10s = %d' % (i + 1,item.title(),round(count)))
# >>>
# #1: Avocados   = 1
# #2: Bananas    = 2
# #3: Cherries   = 15 

第三个缺点是,如果想用同一个值来填充格式字符串里的多个位置,那么必须在 % 操作符右侧的元组中相应地多次重复该值。

template = '%s loves food. See %s cook.'
name = 'Max'
formatted = template % (name, name)
print(formatted)
# >>>
# Max loves food. See Max cook.

如果想在填充之前把这个值修改一下,那么必须同时修改多处才行,例如,如果这次要填的不是 name 而是 name.title(),那就必须提醒自己,要把所有的 name 都改成 name.title()。若是有的地方改了,有的地方没改,那输出的信息可能就不一致了。

template = '%s loves food. See %s cook.'
name = 'brad'
formatted = template % (name.title(), name.title())
print(formatted)
# >>>
# Brad loves food. See Brad cook.

为了解决上面提到的一些问题,Python 的 % 操作符允许我们用 dict 取代 tuple,这样的话,我们就可以让格式字符串里面的说明符与 dict 里面的键以相应的名称对应起来,例如 %(key)s 这个说明符,意思就是用字符串(s)来表示 dict 里面名为 key 的那个键所保存的值。下面通过这种办法解决刚才讲的第一个缺点,也就是 % 操作符两侧的顺序不匹配问题。

key = 'my_var'
value = 1.234
old_way = '%-10s = %.2f' % (key, value)
new_way = '%(key)-10s = %(value).2f' % {'key': key, 'value': value}  # Original
reordered = '%(key)-10s = %(value).2f' % {'value': value, 'key': key}  # Swapped
assert old_way == new_way == reordered

这种写法还可以解决刚才讲的第三个缺点,也就是用同一个值替换多个格式说明符的问题。改用这种写法之后,我们就不用在 % 操作符右侧重复这个值了。

name = 'Max'
template = '%s loves food. See %s cook.'
before = template % (name, name)  # Tuple
template = '%(name)s loves food. See %(name)s cook.'
after = template % {'name': name}  # Dictionary
assert before == after

但是,这种写法会让刚才讲的第二个缺点变得更加严重,因为字典格式字符串的引入,我们必须给每一个值都定义键名,而且要在键名的右侧加冒号,格式化表达式变得更加冗长,看起来也更加混乱。把不采用 dict 的写法与采用 dict 的写法对比一下,可以更明确地意识到这种写法的缺点。

for i, (item, count) in enumerate(pantry):before = '#%d: %-10s = %d' % (i + 1, item.title(), round(count))after = '#%(loop)d: %(item)-10s = %(count)d' % {'loop': i + 1, 'item': item.title(), 'count': round(count)}assert before == after

第四个缺点是,把 dict 写到格式化表达式里面会让代码变多。每个键都至少要写两次:一次是在格式说明符中,还有一次是在字典中作为键,另外,定义字典的时候,可能还要专门用一个变量来表示这个键所对应的值,而且这个变量的名称或许也和键名相同,这样算下来就是三次了。

soup = 'lentil'
formatted = 'Today\'s soup is %(soup)s.' % {'soup': soup}
print(formatted)
# >>>
# Today's soup is 1enti1.

除了要反复写键名,在格式化表达式里面使用 dict 的办法还会让表达式变得特别长,通常必须拆分为多行来写,同时,为了与格式字符串的多行写法相对应,定义字典的时候,也要一行一行地给每个键设定对应的值。

内置的 format 函数与 str 类的 format 方法

Python3 添加了高级字符串格式化(advanced string formatting)机制,它的表达能力比老式 C 风格的格式字符串要强,且不再使用 % 操作符。

下面这段代码,演示了这种新的格式化方式。在传给 format 函数的格式里面,逗号表示显示千位分隔符,^ 表示居中对齐。

a = 1234.5678
formatted = format(a, ',.2f')
print(formatted)b = 'my string'
formatted = format(b, '^20s')
print('*', formatted, '*')
# >>>
# 1,234.57
# *      my string       *

如果 str 类型的字符串里面有许多值都需要调整格式,则可以调用 str 的新 format 方法。该方法不使用 %d 这样的 C 风格格式说明符。而是把格式有待调整的那些位置在字符串里面先用 {} 代替,然后按从左到右的顺序,把需要填写到那些位置的值传给 format 方法,使这些值依次出现在字符串中的相应位置。

key ='my_var'
value = 1.234
formatted = '{} = {}'.format(key, value)
print(formatted)
# >>>
# my_var = 1.234

可以在 {} 里写个冒号,然后把格式说明符写在冒号的右边,用以规定 format 方法所接收的这个值应该按照怎样的格式来调整。

formatted = '{:<10} = {:.2f}'.format(key, value)
print(formatted)
# >>>
# my_var     = 1.23

这种写法的效果可以这样理解:系统先把 str.format 方法接收到的每个值传给内置的 format 函数,并找到这个值在字符串里对应的 {},同时将 {} 里面写的格式也传给 format 函数,例如系统在处理 value 的时候,传的就是 format(value,‘.2f’)。然后,系统会把 format 函数所返回的结果写在整个格式化字符串 {} 所在的位置。另外,每个类都可以通过 __format__ 这个特殊的方法定制相应的逻辑,这样的话,format 函数在把该类实例转换成字符串时,就会按照这种逻辑来转换。

C 风格的格式字符串采用 % 操作符来引导格式说明符,所以如果要将这个符号照原样输出,那就必须转义,也就是连写两个 %。同理,在调用 str.format 的时候,如果想把 str 里面的 {、} 照原样输出,那么也得转义。

print('%.2f%%' % 12.5)
print('{} replaces {{}}'.format(1.23))
# >>>
# 12.50%
# 1.23 replaces {}

调用 str.format 方法的时候,也可以给 str 的 {} 里面写上数字,用来指代 format 方法在这个位置所接收到的参数值位置索引。以后即使这些 {} 在格式字符串中的次序有所变动,也不用调换传给 format 方法的那些参数。于是,这就避免了前面讲的第一个缺点所提到的那个顺序问题。

formatted = '{1} = {0}'.format(key, value)
print(formatted)
# >>>
# 1.234 = my_var

同一个位置索引可以出现在 str 的多个 {} 里面,这些{}指代的都是 format 方法在对应位置所收到的值。这就不需要把这个值重复地传给 format 方法,于是就解决了前面提到的第三个缺点。

name = 'Max'
formatted = '{0} loves food. See {0} cook.'.format(name)
print(formatted)
# >>>
# Max loves food. See Max cook.

然而,这个新的 str.format 方法并没有解决上面讲的第二个缺点。如果在对值做填充之前要先对这个值做出调整,那么用这种方法写出来的代码还是跟原来一样乱,阅读性差。

当然,这种 {} 形式的说明符,还支持一些比较高级的用法,例如可以查询 dict 中某个键的值,可以访问 list 里某个位置的元素,还可以把值转化成 Unicode 或 repr 字符串。下面这段代码把这三项特性结合了起来。

menu = {'soup': 'lentil','oyster': 'kumamoto','special': 'schnitzel',
}
formatted = 'First letter is {menu[oyster][0]!r}'.format(menu=menu)
print(formatted)
# >>>
# First letter is 'k'

但是这些特性,依然不能解决前面提到的第四个缺点,也就是键名需要多次重复的那个问题。所以并不推荐大家用 str.format 方法。当然,还是必须掌握新的格式说明符所使用的这套迷你语言(mini language),可以在 str 的 {} 里面按照这套迷你语言的规则来指定冒号右侧的格式。系统内置的 format 函数也会用到这套规则。

插值格式字符串

Python 3.6 添加了一种新的特性,叫作插值格式字符串(interpolated format string,简称 f-string),可以解决上面提到的所有问题。新语法特性要求在格式字符串的前面加字母 f 作为前缀,这跟字母 b 与字母 r 的用法类似。

f-string 把格式字符串的表达能力发挥到了极致,它彻底解决了上文提到的第四个缺点,也就是键名重复导致的程序冗余问题。可以直接在 f-string 的 {} 里面引用当前 Python 范围内的所有名称,进而达到简化的目的。

key = 'my_var'
value = 1.234
formatted = f'{key} = {value}'
print(formatted)
# >>>
# my_var = 1.234

str.format 方法所支持的那套迷你语言,也就是在 {} 内的冒号右侧所采用的那套规则,现在也可以用到 f-string 里面,而且还可以像早前使用 str.format 时那样,通过 ! 符号把值转化成 Unicode 及 repr 形式的字符串。

formatted = f'{key!r:<10} = {value:.2f}'
print(formatted)
# >>>
# 'my_var' = 1.23

同一个问题,使用 f-string 来解决总是比通过 % 操作符使用 C 风格的格式字符串简单,而且也比 str.format 方法简单。

f_string = f'{key:<10} = {value:.2f}'
c_tuple = '%-10s = %.2f' % (key, value)
str_args = '{:<10} = {:.2f}'.format(key, value)
str_kw = '{key:<10} = {value:.2f}'.format(key=key, value=value)
c_dict = '%(key)-10s = %(value).2f' % {'key': key, 'value': value}
assert c_tuple == c_dict == f_string
assert str_args == str_kw == f_string

在 f-string 方法中,各种 Python 表达式都可以出现在 {} 里,于是这就解决了前面提到的第二个缺点。我们现在可以用相当简洁的写法对需要填充到字符串里面的值做出微调。

pantry = [('avocados', 1.25),('bananas', 2.5),('cherries', 15),
]
for i, (item, count) in enumerate(pantry):old_style = '#%d: %-10s = %d'% (i + 1,item.title(),round(count))new_style = '#{}: {:<10s} = {}'.format(i + 1,item.title(),round(count))f_string = f'#{i+1}: {item.title():<10s} = {round(count)}'assert old_style == new_style == f_string

要是想表达得更清楚一些,可以把 f-string 写成多行的形式,类似于 C 语言的相邻字符串拼接(adjacent-string concatenation)。

pantry = [('avocados', 1.25),('bananas', 2.5),('cherries', 15),
]
for i, (item, count) in enumerate(pantry):print(f'#{i+1}: 'f'{item.title():<10s} = 'f'{round(count)}')
# >>>
# #1: Avocados   = 1
# #2: Bananas    = 2
# #3: Cherries   = 15

Python 表达式也可以出现在格式说明符中。例如,下面的代码把小数点之后的位数用变量来表示,然后把这个变量的名字 places 用 {} 括起来放到格式说明符中,这样写比采用硬代码更灵活。

places = 3
number = 1.23456
print(f'My number is {number:.{places}f}')
# >>>
# My number is 1.235

在 Python 内置的四种字符串格式化办法里面,f-string 可以简洁而清晰地表达出许多种逻辑,这使它成为程序员的最佳选择。如果你想把值以适当的格式填充到字符串里面,那么首先应该考虑的就是采用 f-string 来实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/43948.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

4000字|手把手教你:从0到1搭建跨境电商生意

有小伙伴问我能不能系统的聊下跨境电商的运作思路&#xff0c;因为过去的文章基本都是逐块的在分享各种心得&#xff0c;对于一些想要系统学习跨境电商的朋友来说有点晦涩难懂&#xff0c;刚好赶上羊羊羊&#xff0c;索性花点时间来认真聊聊这个。 在开始聊这个话题之前&#…

探索数据的奥秘:sklearn中的聚类分析技术

探索数据的奥秘&#xff1a;sklearn中的聚类分析技术 在数据科学领域&#xff0c;聚类分析是一种无监督学习方法&#xff0c;它的目标是将数据集中的样本划分为多个组或“簇”&#xff0c;使得同一组内的样本相似度高&#xff0c;而不同组间的样本相似度低。scikit-learn&…

qdma enable jtag debugger

ip上的m_axi_lite 是连接到qdma_v4_0_11_dma5_rtl_top这个ip的 和jtag debugger没有关系 qdma enable jtag debugger 读取的是ip内部reg ///home/nic626/smart_nic/build_dir/qdma_no_sriov_ex/qdma_no_sriov_ex.srcs/sources_1/ip/qdma_no_sriov.xcix!/qdma_no_sriov/ip_0/so…

SQL基础-DQL 小结

SQL基础-DQL 小结 学习目标&#xff1a;学习内容&#xff1a;SELECTFROMWHEREGROUP BYHAVINGORDER BY运算符ASC 和 DESC 总结 学习目标&#xff1a; 1.理解DQL&#xff08;Data Query Language&#xff09;的基本概念和作用。 2.掌握SQL查询的基本语法结构&#xff0c;包括SEL…

Linux文件编程(标准C库)

目录 一、标准C库打开/创建文件&#xff0c;读写文件&#xff0c;光标移动 二、标准C库写入结构体到文件 三、其他函数补充 1.fputc函数 2.feof函数和fgetc函数 前面讲到的open函数都是基于linux内核的&#xff0c;也就是说在Windows系统上无法运行&#xff0c;移植性比较…

使用simulink进行esp32开发,进行串口收发数据需要注意的地方,为什么收发不成功

1&#xff0c;主要是因为simulink里的配置文件配置的波特率和串口接受软件配置的波特不一致导致的 2&#xff0c;主要有以下三个界面 a.配置文件 b.模型 模型直接选择使用的是那组串口就行了&#xff0c;一般情况下我们收发使用同一组就可以&#xff0c;这样收发模块填写的端…

20240711编译友善之臂的NanoPC-T6开发板的Buildroot

20240711编译友善之臂的NanoPC-T6开发板的Buildroot 2024/7/11 21:02 百度&#xff1a;nanopc t6 wiki https://wiki.friendlyelec.com/wiki/index.php/NanoPC-T6/zh NanoPC-T6/zh 4.4 安装系统 4.4.1 下载固件 4.4.1.1 官方固件 访问此处的下载地址下载固件文件 (位于网盘的&q…

社交媒体中智能品牌视觉识别系统的设计与应用

社交媒体中智能品牌视觉识别系统的设计与应用开题报告与任务书分析 一、引言 随着社交媒体的蓬勃发展,品牌与消费者之间的互动日益频繁,品牌视觉识别系统作为品牌传播的重要载体,其设计与应用显得尤为重要。特别是在智能技术的推动下,如何设计并应用智能品牌视觉识别系统…

【Python】已解决:ModuleNotFoundError: No module named ‘sklearn.cross_validation

文章目录 一、问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 &#xff08;机器学习分割数据问题&#xff09;解决“ModuleNotFoundError: No module named ‘sklearn.cross_validation’” 一、问题背景 在机器学习的实践中&#xff0c;数据分割是…

CSS技巧专栏:一日一例 4.纯CSS实现两款流光溢彩的酷炫按钮特效

大家好&#xff0c;今天是 CSS技巧专栏&#xff1a;一日一例 第三篇《纯CSS实现两款流光溢彩的酷炫按钮特效》 先看图&#xff1a; 特此说明&#xff1a; 本专题专注于讲解如何使用CSS制作按钮特效。前置的准备工作和按钮的基本样式&#xff0c;都在本专栏第一篇文章中又详细…

Python基础教学之四:面向对象编程——迈向更高级编程

Python基础教学之四&#xff1a;面向对象编程——迈向更高级编程 一、面向对象编程概念 1. 类和对象 定义&#xff1a;在面向对象编程(OOP)中&#xff0c;类是创建对象的模板&#xff0c;它定义了对象的属性和方法。对象是类的实例&#xff0c;具体存在的实体&#xff0c;拥有…

socket编程(1)

socket编程 1. 预备知识点1.1 网络字节序1.2 ip地址转换函数1.3 sockaddr数据结构 最后 1. 预备知识点 1.1 网络字节序 多字节数据有大端和小端之分&#xff0c;网络数据流采用大端字节序&#xff0c;如果主机采用的是小端字节序&#xff0c;那么需要转换。 大端&#xff1a…

ffmpeg和imagemagick制作gif动图

from: https://blog.csdn.net/hufang360/article/details/107291163?ops_request_misc%257B%2522request%255Fid%2522%253A%2522167876076516800186587476%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id167876076516800186587476&biz_i…

【JavaScript 报错】未捕获的范围错误:Uncaught RangeError

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、错误原因分析1. 递归调用次数过多2. 数组长度超出限制3. 数值超出允许范围 二、解决方案1. 限制递归深度2. 控制数组长度3. 检查数值范围 三、实例讲解四、总结 Uncaught RangeError 是JavaScript中常见的一种错误&…

347. 前 K 个高频元素(中等)

347. 前 K 个高频元素 1. 题目描述2.详细题解3.代码实现3.1 Python3.2 Java 1. 题目描述 题目中转&#xff1a;347. 前 K 个高频元素 2.详细题解 寻找出现频率前 k k k高的元素&#xff0c;因此需要先统计各个元素出现的次数&#xff0c;该步骤时间复杂度为 O ( n ) O(n) O(n)…

柔性接触力学及其建模仿真方法

柔性接触力学是研究柔性体&#xff08;如柔性机器人、柔性结构等&#xff09;在接触过程中产生的力学效应和相互作用的学科。它涉及到接触力的计算、接触变形的分析以及接触过程中的能量转换等多个方面。由于柔性体具有变形能力&#xff0c;其接触过程往往比刚性体接触更为复杂…

Transformer学习过程中常见的问题与解决方案 - Transformer教程

在机器学习领域&#xff0c;Transformer模型已经成为了处理自然语言处理&#xff08;NLP&#xff09;任务的主流工具。然而&#xff0c;在学习和使用Transformer的过程中&#xff0c;很多人会遇到各种各样的问题。今天我们就来聊一聊Transformer学习过程中常见的问题以及对应的…

C++模板总结

文章目录 写在前面1. 函数模板1.1 函数模板的概念1.2 函数模板的原理1.3 函数模板的实例化1.4 函数模板的实例化模板参数的匹配原则 2. 类模板3. 非类型模板参数4. 模板的特化4.1 概念4.2 函数模板特化4.3 类模板特化 5. 模板分离编译6. 总结 写在前面 进入C以后&#xff0c;C…

智能小车——初步想法

需要参考轮趣的智能小车自己搭建一台智能机器人&#xff0c;这里从底层控制开始逐步搭建。 控制模式 之后要自行搭建智能小车&#xff0c;所以将轮趣的底盘代码进行学习&#xff0c;根据开发手册先大致过一遍需要的内容。 有做很多个控制方法&#xff0c;包括了手柄、串口、…

MySQL中的JOIN、LEFT JOIN、RIGHT JOIN讲解

在 MySQL 中&#xff0c;JOIN 是一种非常强大的功能&#xff0c;它允许你将两个或多个表中的行结合起来&#xff0c;基于两个表之间的共同字段。这种操作在数据库查询中非常常见&#xff0c;特别是在处理关系型数据库时。下面我将分别解释 JOIN、LEFT JOIN&#xff08;也称为 L…