30.1.4. 格式化字符的多种方式¶
1. %的方式格式化¶
格式说明符的写法来自C语言的printf函数,Python语言以及其他一些编程语言,都依照那套写法来规定自己的格式字符串。所以,常见的printf选项都可以当成Python的格式说明符来用,例如%s、%x、%f等,此外还可以控制小数点的位值,并指定填充与对齐方式。许多Python新手程序员都喜欢用C风格的格式字符串,因为他们比较熟悉这种风格,而且这样写起来比较简单。
a = 0b10111011
b = 0xc5f
print("Binary is %d, hex is %d" % (a, b))
如下所示,如果把key跟value互换位置,那么程序就会在运行时出现异常。
key = "my_var"
value = 1.234
formatted = "%-10s = %.2f" % (key, value)
print(formatted)
要想避免这种问题,必须经常检查%操作符左右两侧的写法是否相互兼容。这个过程很容易出错,因为每次修改完之后都要手工检查一遍。
如果想让打印出来的信息更好懂,那可能得把这几个值稍微调整一下,但是调整之后,%操作符右侧的那个三元组就特别长,所以需要多行拆分才能写得下,这会影响程序的可读性。
for i, (item, count) in enumerate(pantry):
print("#%d: %-10s = %.2f" % (i, item, count))
for i, (item, count) in enumerate(pantry):
print("#%d: %-10s = %.2f" % (
i + 1,
item,
count))
如果要把所有的name都改成name.title()。若是有的地方改了,有的地方没改,那输出的信息可能就不一致了。
var_s = "%s love food ,See %s cook."
name = "hujianli"
formatted = var_s % (name, name)
print(formatted)
var_s = "%s love food ,See %s cook."
name = "hujianli"
formatted = var_s % (name.title(), name.title())
print(formatted)
Python的%操作符允许我们用dict取代tuple,这样的话,我们就可以让格式字符串里面的说明符与dict里面的键以相应的名称对应起来,例如%(key)s这个说明符,意思就是用字符串(s)来表示dict里面名为key的那个键所保存的值。
key = "my_var"
value = 1.234
formatted = "%(key)-10s = %(value).2f" % {"key": key, "value": value}
print(formatted)
因为字典格式字符串的引入,我们必须给每一个值都定义键名,而且要在键名的右侧加冒号,格式化表达式变得更加冗长,看起来也更加混乱
for i, (item, count) in enumerate(pantry):
print("#%(loop)d: %(item)-10s = %(count).2f" % {
"loop": i + 1,
"item": item,
"count": count
})
缺点如下:
反复写键名,在格式化表达式里面使用dict的办法还会让表达式变得特别长,通常必须拆分为多行来写,同时,为了与格式字符串的多行写法相对应,定义字典的时候,也要一行一行地给每个键设定对应的值。
如果要对键名稍做修改,那么必须同步修改格式字符串里的说明符,这更让代码变得相当烦琐,可读性更差。
2.内置的format函数与str类的format方法¶
Python 3添加了高级字符串格式化(advanced string formatting)机制,它的表达能力比老式C风格的格式字符串要强,且不再使用%操作符。
我们针对需要调整格式的这个Python值,调用内置的format函数,并把这个值所应具备的格式也传给该函数,即可实现格式化。
在传给format函数的格式里面,逗号表示显示千位分隔符,^表示居中对齐。
a = 1234.5678
formatted = format(a, ",.2f")
print(formatted)
b = "my string"
formatted = format(b, "^20s")
print("*", formatted, "*")
key = "my var"
value = 123.456
formatted = "{} = {}".format(key, value)
print(formatted)
formatted = "{:<10} = {:.2f}".format(key, value)
print(formatted)
调用str.format方法的时候,也可以给str的{}里面写上数字,用来指代format方法在这个位置所接收到的参数值位置索引。以后即使这些{}在格式字符串中的次序有所变动,也不用调换传给format方法的那些参数。
key = "my var"
value = 123.456
formatted = "{1} = {0}".format(key, value)
print(formatted)
同一个位置索引可以出现在str的多个{}里面,这些{}指代的都是format方法在对应位置所收到的值。这就不需要把这个值重复地传给format方法
name = "hujianli"
formatted = "{0} is love food See {0} cook..".format(name)
print(formatted)
把原来那种写法和现在的新写法对比一下,大家就会看到新写法并不比原来好多少。
for i, (item, count) in enumerate(pantry):
old_style = "#%d: %-10s = %d" % (
i + 1,
item.title(),
round(count)
)
print(old_style)
new_style = "#{}:{:<10s} = {}".format(
i+1,
item.title(),
round(count)
)
print(new_style)
str.format方法还是没有能够把Python表达式的优势充分发挥出来。
old_template = (
'Today\'s soup is %(soup)s, '
'buy one get tow %(oyster)s oysters, '
'and our special entree is %(special)s.'
)
old_formatted = old_template % {
"soup": "lentil",
"oyster": "kuma",
"special": "shenzen",
}
new_template = (
'Today\'s soup is {soup}, '
'buy one get tow {oyster} oysters, '
'and our special entree is {special}.'
)
new_formatted = new_template.format(
soup="lentil",
oyster="kumamo",
special="shenzen",
)
print(old_formatted)
print(new_formatted)
因为str.format方法有这样的一些缺点,而且没办法解决早前提到的第二个与第四个缺点,所以总体来说,笔者并不推荐大家用str.format方法。
3.插值格式字符串¶
Python 3.6添加了一种新的特性,叫作插值格式字符串(interpolated formatstring,简称f-string),可以解决上面提到的所有问题。新语法特性要求在格式字符串的前面加字母f作为前缀,这跟字母b与字母r的用法类似,也就是分别表示字节形式的字符串与原始的(或者说未经转义的)字符串的前缀。
key = "my_var"
value = 1.234
formatted = f'{key} = {value}'
print(formatted)
str.format方法所支持的那套迷你语言,也就是在{}内的冒号右侧所采用的那套规则,现在也可以用到f-string里面,而且还可以像早前使用str.format时那样,通过!符号把值转化成Unicode及repr形式的字符串。
formatted = f'{key!r:<10} = {value:.2f}'
print(formatted)
pantry = [
("avocados", 1.25),
("bananas", 2.5),
("cherries", 15)
]
for i, (item, count) in enumerate(pantry):
print("#%d: %-10s = %d" % (
i + 1,
item.title(),
round(count)))
print("*" * 100)
for i, (item, count) in enumerate(pantry):
print("#{}: {:<10s} ={}".format(
i + 1,
item.title(),
round(count)))
print("*" * 100)
for i, (item, count) in enumerate(pantry):
print(f"#{i + 1}: {item.title():<10s} ={round(count)}")
#或者写多行
for i, (item, count) in enumerate(pantry):
print(f"#{i + 1}: "
f"{item.title():<10s} ="
f"{round(count)}")
Python表达式也可以出现在格式说明符中。例如,下面的代码把小数点之后的位数用变量来表示,然后把这个变量的名字places用{}括起来放到格式说明符中,这样写比采用硬代码更灵活。
plances = 3
number = 1.23456
print(f"My number is {number:.{plances}f}")
总结
采用%操作符把值填充到C风格的格式字符串时会遇到许多问题,而且这种写法比较烦琐。
str.format方法专门用一套迷你语言来定义它的格式说明符,这套语言给我们提供了一些有用的概念,但是在其他方面,这个方法还是存在与C风格的格式字符串一样的多种缺点,所以我们也应该避免使用它。
f-string采用新的写法,将值填充到字符串之中,解决了C风格的格式字符串所带来的最大问题。f-string是个简洁而强大的机制,可以直接在格式说明符里嵌入任意Python表达式。