f-string: 改进的 Python 字符串格式化语法
f-strings 是 Python 3.6 引入的一种非常棒的字符串格式化方法。
相比其他格式方法,f-strings 更易读、更简洁、更少犯错,也更快。
在了解为什么以及如何使用 f-strings 之前,我们先看一下在 f-strings 之前 Python 是如何进行字符串格式化的。那是一段很艰难的日子,犹如大雪中艰难攀爬上下学路上的山坡。
【Python 中字符串格式的老式方法】
Python 3.6 之前,有两种主要方式可将 Python 表达式嵌入到常量字符串中进行格式化:% 格式化语法和 str.format() 方法。
我们来看一下这两种方法的使用方法和局限之处。
1,% 格式化语法
这是 Python 中最古老的一种格式化方法,从一开始就存在于语言规范中。你可以在 Python 官方文档中获取更多相关信息。
你需要知道,官方文档并不推荐 % 格式化方法,并给出如下理由:
那么,如何使用 % 格式化语法呢?
string 类型的对象支持使用 % 运算符来执行字符串格式化操作。
例如:
>>> name = 'Eric'
>>> 'Hello, %s' % name
'Hello, Eric'
如果要插入多个变量,你必须使用 tuple 来包含这些变量。
>>> name = 'Eric'
>>> age = 21
>>> 'Hello %s. You are %s.' % (name, age)
'Hello Eric. You are 21.'
上边这两段代码可读性还是不错的,但是,一旦你需要使用更多的变量和更长的格式字符串,代码的可读性就变得差很多了。
>>> first_name = 'Eric'
>>> last_name = 'Idle'
>>> age = 21
>>> profession = 'comedian'
>>> affiliation = 'Monty Python'
>>> "Hello, %s %s. You are %s. You are a %s. You were a member of %s." % (first_name, last_name, age, profession, affiliation)
'Hello, Eric Idle. You are 21. You are a comedian. You were a member of Monty Python.'
这段代码开始显得冗长,而且容易产生错误:你可能在格式字符串中漏写一个 %,或者未能正确显示 tuple 或 dict 对象。
幸运的是,格式化之路前途光明。
2,str.format() 方法
这是 Python 2.6 引入的新式格式化方法,能胜任 % 完成的所有工作。
str.format() 是 % 格式化语法的一个改进。它采用了正常的函数调用语法,并且可通过待格式化对象的 __format__() 方法进行扩展。
str.format() 在格式字符串中使用大括号为待格式化对象预留替换位。
>>> "Hello, {}. You are {}.".format(name, age)
'Hello, Eric. You are 21.'
可以通过变量在 str.format() 参数中的位置索引来引用这些对象。
>>> "Hello, {1}. You are {0}.".format(age, name)
'Hello, Eric. You are 21.'
也可以在格式字符串中插入变量名,这样你就能够传递对象,并在格式字符串的大括号里引用参数和方法。
>>> person = {'name': 'Eric', 'age': 21}
>>> "Hello, {name}. You are {age}.".format(name=person['name'], age=person['age'])
'Hello, Eric. You are 21.'
>>> "Hello, {name}. You are {age}.".format(age=person['age'], name=person['name'])
'Hello, Eric. You are 21.'
>>>
>>> name = 'Eric'
>>> age = 21
>>> "Hello, {name1}. You are {age1}.".format(age1=age, name1=name)
'Hello, Eric. You are 21.'
对于字典(dict)类型的变量,也可以使用 ** 来实现这个操作:
>>> person = {'name': 'Eric', 'age': 21}
>>> "Hello, {name}. You are {age}.".format(**person)
str.format() 相比 % 格式化语法无疑是一个升级,但难称完美。
插入变量名使得它比 % 格式化语法更易读;可在参数中对字典类型对象执行解压操作,使得代码更简洁。但是处理多个参数或较长字符串时同样显得冗长。
>>> first_name = "Eric"
>>> last_name = "Idle"
>>> age = 21
>>> profession = "comedian"
>>> affiliation = "Monty Python"
>>> "Hello, {first_name} {last_name}. You are {age}.You are a {profession}. You were a member of {affiliation}.".format(first_name=first_name, last_name=last_name, age=age, profession=profession, affiliation=affiliation)
'Hello, Eric Idle. You are 21.You are a comedian. You were a member of Monty Python.'
【f-strings:一种新的改进的字符串格式化方法】
f-strings 是 Python 3.6 引入的一种新式字符串格式化方法,使得字符串格式化工作更简单。
f-strings 也称为格式化的字符串字面量,它以 f 开头,在引号包含的字符串中使用大括号包含待求值的表达式。这些表达式在运行时求值,然后通过 __format__ 协议实现格式化。
一起看一下 f-strings 是如何简化格式化操作的吧。
1,简单语法
f-strings 的基本语法和 str.format() 相似,但是更简洁。
>>> name = 'Eric'
>>> age = 21
>>> f'hello, {name}. You are {age}.'
'hello, Eric. You are 21.'
只需直接写一遍变量名即可。
也可以使用大写的 F 作为前缀:
>>> F"Hello, {name}. You are {age}."
'Hello, Eric. You are 21.'
2,支持各种表达式
由于 f-strings 是在运行时求值,你可以在其中放置任意合法的 Python 表达式,这让你可以做一些很巧妙的事情。
你可以做一些很直接的事情:
>>> f'{2 * 37}'
'74'
也可以调用函数:
>>> def to_lowercase(input):
... return input.lower()
...
>>> name = "Eric Idle"
>>> f"{to_lowercase(name)} is funny."
'eric idle is funny.'
还可以直接调用类方法:
>>> f"{name.lower()} is funny."
'eric idle is funny.'
你甚至可以在 f-strings 中格式化较复杂的对象,只要对象的类中恰当运用了 f-strings。
假设你有一个下边这样的类:
class Comedian:
def __init__(self, first_name, last_name, age):
self.first_name = first_name
self.last_name = last_name
self.age = age
def __str__(self):
return f"{self.first_name} {self.last_name} is {self.age}."
def __repr__(self):
return f"{self.first_name} {self.last_name} is {self.age}. Surprise!"
你可以这样直接格式化这个类的对象:
>>> new_comedian = Comedian("Eric", "Idle", "21")
>>> f"{new_comedian}"
'Eric Idle is 21.'
__str__() 和 __repr__() 方法用于处理对象如何表示为字符串,你至少应该在类定义中包含其中一个方法。如果只定义一个的话,请使用 __repr__(),因为它可用来替代 __str__()。
__str__() 返回的字符串是一个对象的非正式字符串表示,应该是可读的。__repr__() 返回的字符串是正式的表示,应该是明确的。
调用 str() 和 repr() 方法比直接使用 __str__() 和 __repr__() 要好。
默认情况下,f-strings 会使用 __str__() 来输出对象的字符串表示,但是如果你加入了转换标志 !r,f-strings 就会调用 __repr__()。
>>> f"{new_comedian}"
'Eric Idle is 21.'
>>> f"{new_comedian!r}"
'Eric Idle is 21. Surprise!'
3,多行 f-strings
你可以在 f-strings 中使用多行字符串。
>>> name = "Eric"
>>> profession = "comedian"
>>> affiliation = "Monty Python"
>>> message = (
... f"Hi {name}. "
... f"You are a {profession}. "
... f"You were in {affiliation}."
... )
>>> message
'Hi Eric. You are a comedian. You were in Monty Python.'
但是请注意,你需要在多行字符串的每一行之前放置 f 前缀。否则,格式化结果会出错:
>>> message = (
... f"Hi {name}. "
... "You are a {profession}. "
... "You were in {affiliation}."
... )
>>> message
'Hi Eric. You are a {profession}. You were in {affiliation}.'
这段代码只有第一个变量名被替换为实际值。
你也可以使用转义符 \ 将 f-strings 扩展到多行。
>>> message = f"Hi {name}. " \
... f"You are a {profession}. " \
... f"You were in {affiliation}."
>>>
>>> message
'Hi Eric. You are a comedian. You were in Monty Python.'
如果使用三引号字符串来定义多行 f-strings,输出的格式化字符串也将是多行的。
>>> message = f"""
... Hi {name}.
... You are a {profession}.
... You were in {affiliation}.
... """
>>> message
'\n\tHi Eric. \n\tYou are a comedian. \n\tYou were in Monty Python.\n'
4,运行速度
f-strings 中的 f 也可以代表 “fast”。通常情况下,f-strings 比 % 格式化语法和 str.format() 都要快。
如你所见,f-strings 实际上是一个运行时求值的表达式,而不是常量值。
运行时,大括号中的表达式先在其自己的作用域中求值,然后和 f-strings 的字符串常量部分组合在一起,并返回结果字符串。这就是 f-strings 的全部工作。
这里给出一个比较三种格式化方式速度的例子。
>>> import timeit
>>> timeit.timeit("""name = "Eric"
... age = 74
... '%s is %s.' % (name, age)""", number = 10000)
0.003324444866599663
>>> timeit.timeit("""name = "Eric"
... age = 74
... '{} is {}.'.format(name, age)""", number = 10000)
0.004242089427570761
>>> timeit.timeit("""name = "Eric"
... age = 74
... f'{name} is {age}.'""", number = 10000)
0.0024820892040722242
可以看出,f-strings 速度较快。
但结果并非总是如此,不同版本的 Python 中,str.format() 有时候会快一些。
【f-strings 的一些语法细节】
我们已经了解到 f-strings 拥有简洁的语法和较快的速度,在开始使用 f-strings之前,再来了解几点细节知识。
1,引号
在 f-strings 表达式中可以使用多种引号,但是 f-strings 内部表达式使用的引号和 f-strings 外部的引号不能相同。
下边这两种写法都是正确的:
>>> f"{'Eric Idle'}"
'Eric Idle'
>>> f'{"Eric Idle"}'
'Eric Idle'
也可以使用三引号:
>>> f"""Eric Idle"""
'Eric Idle'
>>> f'''Eric Idle'''
'Eric Idle'
如果你想在 f-strings 的内部和外部使用相同的引号,你需要使用转义符 \ 来转义内部引号:
>>> f"The \"comedian\" is {name}, aged {age}."
'The "comedian" is Eric, aged 21.'
2,字典变量
当你打算在 f-strings 中插入字典变量的某些 key 时,若你使用单引号引用 key 名,你需要在 f-strings 的外部使用双引号。
>>> comedian = {'name': 'Eric Idle', 'age': 21}
>>> f"The comedian is {comedian['name']}, aged {comedian['age']}."
'The comedian is Eric Idle, aged 21.'
若你在 f-strings 的外部使用了和引用字典 key 相同的单引号,Python 解释器会报错:
>>> comedian = {'name': 'Eric Idle', 'age': 21}
>>> f'The comedian is {comedian['name']}, aged {comedian['age']}.'
File "<stdin>", line 1
f'The comedian is {comedian['name']}, aged {comedian['age']}.'
^
SyntaxError: invalid syntax
所以,在 f-strings 中插入字典的 key 时,需要特别留意引号的使用方法。
3,大括号
由于 f-strings 使用大括号来插入变量,如果想在 f-strings 中输出 {} 字符,你需要使用双重大括号。
>>> f"{{70 + 4}}"
'{70 + 4}'
但是,如果使用三重大括号,格式化结果中只会保留一重大括号:
>>> f"{{{70 + 4}}}"
'{74}'
如果使用超过三重的大括号,你会在输出中得到更多大括号。
>>> f"{{{{70 + 4}}}}"
'{{70 + 4}}'
4,反斜杠
>>> name = 'Eric'
>>> f"The \"comedian\" is {name}."
'The "comedian" is Eric.'
>>> f"{\"Eric\"}"
File "<stdin>", line 1
SyntaxError: f-string expression part cannot include a backslash
>>> f"Eric is {2 * 37 #Oh my!}."
File "<stdin>", line 1
SyntaxError: f-string expression part cannot include '#'
【结语】
f-strings 作为新式字符串格式化方法,在简洁性、可读性和运行速度上都优于其他方法,建议使用 Python 3.6 以上版本的小伙伴优先采用此方法来格式化字符串。