从 Hello world 开始讲 Python 字符串的格式化|Python 主题月

493 阅读11分钟

本文正在参加「Python主题月」,详情查看 活动链接

一切的开始

每一个 Python Learner,学的第一句话便是让 Python 向这个世界问好。这句话我印象非常深刻,因为 Python 是我最早接触的编程语言,这句话也算是我编程语言学习中的 “妈妈” 了:

# Python 2
print "Hello, world!"

# 学校里教的是 Python 2,但其实下面的 Python 3 语句应该更常用:
print("Hello, world!")

就像英文单词中的 Abandon 一样,最初接触的事物往往最亲切,也最为大家所津津乐道。然而简单的一句 Hello world,其背后却包含了字符串和输出两个深挖下去三天三夜也讲不完的东西。其实,当你看到屏幕上出现 Python 对世界的问候时,你已经实现了对字符串的成功运用了。

但是实际的开发中,我们肯定不可能一直让 Python 就这样牙牙学语,而是需要将一些变量中蕴含的信息按照一定的要求填入字符串。虽然 Python 提供了完备的类型转换系统可以让你先把变量变成字符串再和上下文连接起来,但为了不折磨自己,也不折磨读代码的人,这种情况还是推荐用格式化的方式生成目标字符串。

下面列出了 Python 中的三种字符串格式化方法,同时会以 Hello world 相关的输出为例,给出可以直接执行的代码。那么,让我们开始吧:)

注意:这里使用的是 Python 3

格式化初探:%

第一种方法是 Python 中最早出现的,即 %-formatting。在讲解这一标记用法的同时,我也会简单介绍一下字符串格式化的基本功能。

标记 % 的基本用法是,在字符串中,将需要格式化的变量处用 %[选项]<字母> 的形式标记出来,再在整个字符串后面加上 % <变量>,即:

# 如果变量只有一个,写成:
"上文 %[选项]<字母> 下文" % <变量>

# 如果变量有多个,写成:
"上文 %[选项1]<字母1> 中间1 %[选项2]<字母2> 中间2 ..." % (<变量1>, <变量2>, ...)

每一处的字母表示了后面的变量应当以什么样的格式替换掉字符串里面的标记。%s 表示字符串,%d 表示整数,%f 表示浮点,这三个比较常用,在本节最后也会把更完整的标记含义用表格列出来。

如果看到这里感觉一头雾水的话,不要紧,我们来具体实践一下!

字符串

假如现在你想要 Python 不仅会向世界问好,而是指定一个名字,Python 就可以向这个指定的人问好。这一功能实现起来非常简单,按照上面提到的形式,这样写就可以了:

name = 'Li Hua'

# 无情的问好机器
print('Hello, %s!' % name)

# 输出:Hello, Li Hua!

上面输出的语句里,% 后面的变量会将前面字符串内 % + 字母 的部分替换掉并输出,就相当于 'Hello, ' + name + '!',当然后者能够实现是得益于 Python 强大的字符串处理机制。

浮点和整数

再进一步,如果你在向朋友问好的基础上,需要告诉 ta 一些数字化的信息,比如衬衫的价格,那么就要用到整型 %d 和浮点格式 %f 了。计算也可以放到输出这一步来完成。

name = 'Li Hua'
price = 9.15
n = 2

# 李华,衬衫的价格为 9 磅 15 便士,2 件一共 £18.3!
s = 'Hello, %s! The price of the shirt is £%.2f. So %d shirts will cost you £%.1f.' % (name, price, n, n*price)

print(s)
# 输出:Hello, Li Hua! The price of the shirt is £9.15. So 2 shirts will cost you £18.3.

这里出现了 4 个 % 标记,于是在最后的标记区也应有同样数量的变量与之先后对应,这些变量按照标记的格式填充进对应位置。仔细看看这个 %.2f,按照最开始说的格式,.2 就是所谓的选项了。这个选项表示的是保留两位小数,点几就是保留几位,遵从四舍五入原则。

占位符

其实这里的选项除了可以规范输出本身,还可以同时规范整个插入结果的宽度,如果转化结果不够宽,则会用占位符补齐。这一宽度用数字表示,插入在上面说的选项中(浮点格式在小数点前),如 %3s%4d%6.2f。特别的,整型和浮点型是可以用 0 做占位符的,要实现这一点只需要在 % 和表示宽度的数字前加上 0.

# 字符串,以空格为占位符
s = 'world'
print('Hello,%s.\nHello,%6s!' % (s, s))
# 输出中第二行会比第一行多一个空格。

# 整数,以空格或 0 为占位符
num = 4.525
print('Spaces%6dhere and zeros%03dhere' % (num, num))
# 输出:Spaces     4here and zeros004here

# 浮点数,以空格或 0 为占位符
print('Spaces%6.2fhere and zeros%07.1fhere' % (-num, -num))
# 输出:Spaces -4.53here and zeros-0004.5here
# 注意这里算宽度的时候纳入了小数点和负号,以及注意负号的位置

% 小结

好啦,到这里想必你已经对 Python 的格式化输出,尤其是 %-formatting 有大体的认知了。简单地说,Python 的格式化字符串就是在字符串中预留好变量的位置,再将变量的值以某种格式填充进去

下面这张表则列出了 % 可以提供的标记种类、可选项以及相应的示例。当然这里的并不全面,更全面的格式列表可以参考这个链接

标记含义选项示例
%s字符串<num> :总宽度不低于 ,不足部分用空格补齐'%12s' % 'hello' => 7*' '+'hello'
%d十进制整数<num>%s;0<num> 同样总宽度不低于 ,只是不足部分用 '0' 补齐'%5d %07d'%(123, 442) => ' 123 0000442'
%f浮点[0]<num1>.<num2>:小数点前部分同 %d,小数点后的部分表示保留的小数位数,四舍五入。'%03.2f' % 123.444 => '123.44'
%e / %Ee / E 表示科学计数法%f'%010.2e' % 12363 => '001.24e+10'
%o八进制整数%d'%05o' % 100 => '00144'
%x / %X十六进制整数,x 表示字母小写,X表示字母大写%d'%04x' % 108 => '006c''%04X' % 108 => '006C'

'{}'.format()

大体对 Python 的格式化处理有所了解后,我们来聊一聊其它的格式化方法。 % 标记方法最大的问题在于可读性差,一旦 % 多了之后,就得从前到后一个一个对,很是麻烦。而另一种常用的格式化方法即 format() 即可以解决这个问题。

这个函数是在 2.6 版本引入的,作为 Python 内建类型字符串类的方法,在字符串后面加上 .format() 即可调用。它要配合字符串内的 {} 来使用。{} 就可以理解为前一节所说的 %format() 函数内的变量将会按照给定格式替换掉字符串里的 {},从而实现格式化,如:

print('Hello, {}!'.format('world')) # 输出 Hello, world!

优势

有了前一节 % 的基础,要理解这一节起来想必就不是什么难事了。format() 相比于 %,提供了更加舒适的索引机制。具体来说:

  1. 可以用数字对变量进行索引

看下面的例子:

# 还是 Hello world
name0 = 'world'
name1 = 'Li Hua'

print('Hello, {0}! Hello, {1} and hello {0} again!'.format(name0, name1))
# 输出:Hello, world! Hello, Li Hua and hello world again!

你会发现所有的 {0} 被替换为了 name0,而所有的 {1} 被替换为了 name1。花括号内的索引对应了后面format 函数内传入的参数。

  1. 可以设置参数

这种方式让format 的可读性往上升了一个台阶。我们可以给每个花括号里面加上自定义的参数名,再在后面指定每个参数的值。我们将前一节李华买衬衫的对话重写一遍,可以感受一下可读性的提升:

# 变量
name = 'Li Hua'
price = 9.15
n = 2

# 对话
s = 'Hello, {name}! The price of the shirt is £{price}. So {n} shirts will cost you £{total}.'.format(name=name, price=price, n=n, total=n*price)

print(s)
# 输出:Hello, Li Hua! The price of the shirt is £9.15. So 2 shirts will cost you £18.3.

这种感觉有点像完形填空中,给你提示了每一个空应该填什么。只要变量的命名是按照常理来的,那么其他人在看这个字符串的时候,不需要看后面的 .format(...) 就能大致 get 到这句话的意思。

  1. 和字典、列表、类都有很好的兼容性

关于这一点,看下面的示例代码就好:

# 字典
my_dict = {"name":"Li Hua", "price": 9.15}
# ** 表示拆解
print("Hello, {name}! The price of the shirt is {price}".format(**my_dict))

# 列表
my_list1 = ["Li Hua", 9.15]
my_list2 = [2, 2*9.15]
# 0[0] 第一个 0 表示列表
print("Hello, {0[0]}! The price of the shirt is £{0[1]}. So {1[0]} shirts will cost you £{1[1]}.".format(my_list1, my_list2))

# 类 & 对象
class Shopping:
    def __init__(self, name, price, num):
        self.name = name
        self.price = price
        self.num = num
        self.total_cost = num * price
    
lihua_shopping = Shopping('Li Hua', 9.15, 3)
# 这里 {0.name} 中的 0 是可以省略的!
print("Hello, {0.name}! The price of the shirt is £{0.price}. So {0.num} shirts will cost you £{0.total_cost:.2f}.".format(lihua_shopping))

数字格式

你可能已经注意到了最后一行的 {0.total_cost:.2f},这是 format 函数提供的格式化选项。选项在大括号内,索引或者变量的后方,以 : 分隔并加入。如 {:.2f} 表示保留两位小数,{0>2d} 表示左边用 0 填充到宽度至少为 2 的整数,{:.2%} 表示保留两位小数的百分数等等。详细的参数表在菜鸟教程里有,我这儿偷个懒,就不写了~

f-strings

这种格式化方法则更加年轻,它诞生于 3.6 版本,具有前两种格式化方法的完备功能,同时在表达上更加简洁。例如我们的 Hello world,在前面定义好了 name='world' 时,就只需要在字符串前面加一个 f,并在内部的 {} 中填入变量名就可以了:

print(f'Hello, {name}!')

打下这简简单单的一句,屏幕上就会出现 Hello, world! 了。

使用示例

这一表达方式用到的字符串内标记和 format 有些相似,但是它不需要指明这些变量的取值。实际上,f-strings 中的 {} 内部分可以直接理解为 Python 语句,因为这里面不仅可以是变量,它还可以是表达式、函数、对象,任何返回结果可以变成字符串的语句都可以放在这里。

不知道你有没有注意到,上面李华买衬衫的对话里上有一个 bug,那就是 n=1 时后面的 shirt 应该是单数形式。用 f-strings 就可以轻松解决这个问题,同时对价格的计算也可以包含在字符串内:

s = f'Hello, {name}! The price of the shirt is £{price}. So {n} shirt{(n>1)*"s"} will cost you £{n*price}.'

print(s)

如果有定义好的函数,在 f-strings 中也是可以直接调用的:

def square(n):
    """取平方"""
    return n*n

n = 3
print(f'{n} 的平方是 {square(n)}.')
# 输出:3 的平方是 9.

甚至于,如果你为一个类定义好了 __str__ ,你甚至可以直接将这个类的对象直接插入到 f-strings 的花括号中:

class Student:
    def __init__(self, name, id_num):
        self.name = name
        self.id_num = id_num
        
    def __str__(self):
        return f"Name: {self.name}, ID: {self.id_num}"


lihua = Student('Li Hua', '20210713')
print(f"Following is the info of the student:\n{lihua}.")
# {lihua} 相当于 {str(lihua)}

详情请戳

同样地,f-strings 也可以为数字定义输出的格式,而且和 format 一样都是用 : 分隔的。这一点和 format 颇有些相似,我找到了一篇比较详细的文档,来自本特利大学官网,感兴趣的小伙伴可以去读一读。

另外,f-strings 不仅表达简洁,其运行速度相比于前两种方法也更快。因此,只要 Python 的版本是支持的,就十分推荐使用 f-strings 进行格式化。

写在最后

从 Python 的三种格式化方式的发展变化中,我们也可以窥见开发者对于编程语言的需求,那便是可读性。从按顺序插入参数,到直接将 Python 表达式蕴含在字符串中,Python 的格式化正在一步步变得平易近人。有一句很有趣的话,我是在《Practial Vim》这本书里看到的,不能说有点偏激,只能说非常合适:

代码首先是给人看的,其次是可以运行。

最后,希望本文能在你学习 Python 的过程中有所帮助。❤

参考链接

python基础_格式化输出(%用法和format用法) - RuiWo - 博客园 (cnblogs.com)

Python 字符串 | 菜鸟教程 (runoob.com)

Python format 格式化函数 | 菜鸟教程 (runoob.com)

python格式化字符串f-strings - 追赶菜鸟 - 博客园 (cnblogs.com)

A Guide to f-string formatting in Python - Bentley Univ.