Python字符串格式化问题:%、format()与f-strings
本文为译文,原文 by Joanna Jablonski,地址: https:// realpython.com/python-f -strings/
Python 3.6 引入了新的字符串格式化方式 f-strings,与其它格式化方式相比,不仅简洁明了,可读性更好,更不容易出错,而且运行效率也更高。
你应该马上开始使用这种新的格式化方式,本文将解释其原因与具体用法。
但在此之前,让我们先看一下 f-strings 之前的字符串格式化方式。
Python 字符串格式化的“旧式”做法
在 Python 3.6 之前,把 Python 表达式嵌入字符串,主要有两种方式: % 或
str.format()
。它们的用法与主要不足如下:
选项1:使用 %
这是最原始的、 Python 官方指南所用的格式化方式,相关信息可以阅读 官方文档 。值得注意的是,官方文档其实并不推荐使用这种方式,相关说明如下:
“此处描述的格式化操作存在一些奇怪之处,容易导致一些常见错误(比如无法正确格式化元组与字典等)。
使用新的格式化方式或str.format()
函数可以避免这些错误。这些格式化方式不仅更强大,而且更灵活,更易于扩展。 ( 来源 )
如何使用 % 进行格式化
字符串对象有一个内建操作符
%
,可以用于格式化操作,具体用法如下:
>>> name = "Eric"
>>> "Hello, %s." % name
'Hello, Eric.'
如果要在字符串中嵌入多个变量,则必须使用元组,例如:
>>> name = "Eric">>> age = 74
>>> "Hello, %s. You are %s." % (name, age)
'Hello Eric. You are 74.'
% 格式化的问题
上面的代码看起来还不错,但如果在更长的字符串中嵌入更多变量,代码的可读性就会成为一个问题:
>>> first_name = "Eric"
>>> last_name = "Idle"
>>> age = 74
>>> 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 74. You are a comedian. You were a member of Monty Python.'
因此,这种格式化方式的主要问题是过于繁琐,容易出错,不能正确格式化元组与字典。
选项2:str.format()
这种方式是在 Python 2.6 引入的,可以在 官方文档 找到相关介绍。
str.format() 的用法
str.format()
方式是对 % 方式的一次改进。用户可以通过修改对象的
__format__()
方法来自定义
格式化方式
。
通过大括号,我们可以在字符串中嵌入变量:
>>> "Hello, {}. You are {}.".format(name, age)
'Hello, Eric. You are 74.'
也可以指定变量的引用顺序:
>>> "Hello, {1}. You are {0}.".format(age, name)
'Hello, Eric. You are 74.'
或者直接使用变量名称:
>>> person = {'name': 'Eric', 'age': 74}
>>> "Hello, {name}. You are {age}.".format(name=person['name'], age=person['age'])
'Hello, Eric. You are 74.'
在引用字典时,可以用
**
操作符进行字典拆包:
>>> person = {'name': 'Eric', 'age': 74}
>>> "Hello, {name}. You are {age}.".format(**person)
'Hello, Eric. You are 74.'
总的来说,
str.format()
是 % 格式化方式的一次升级,但依然存在一些问题。
str.format() 的问题
和 % 方式相比,使用
str.format()
的代码可读性要好很多。但如果在较长的字符串中嵌入多个变量,依然会显得繁琐:
>>> first_name = "Eric"
>>> last_name = "Idle"
>>> age = 74
>>> profession = "comedian"
>>> affiliation = "Monty Python"
>>> print(("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 74. You are a comedian. You were a member of Monty Python.'
当然,如果我们提前把需要嵌入的变量放在一个字典中,就可以通过
**
进行拆包操作,从而显得简洁一些。
但一定还存在着更好的格式化方式。
f-Strings:一种改进版格式化方式
Python 3.6 引入了新的字符串格式化方式,这种方式来自于 Eric V. Smith 在 2015 年 8 月提出的方案,具体可以参考 PEP 498 。
f-strings 也称作“格式化的字符串字面量”,它是一个带有
f
前缀的字符串,通过大括号嵌入所需的 Python 表达式,这些表达式的具体值是在运行时确定的,背后依赖的也是嵌入对象的
__format()__
接口。查看
官方文档
可以获得更多信息。
以下是一些具体使用方式:
最简单的句法
f-strings 的句法类似于
str.format()
,但要更简洁,你可以感受一下它的可读性:
>>> name = "Eric"
>>> age = 74
>>> f"Hello, {name}. You are {age}."
'Hello, Eric. You are 74.'
前缀
f
也可以使用大写的
F
。
>>> F"Hello, {name}. You are {age}."
'Hello, Eric. You are 74.'
你是不是已经开始喜欢上这种格式化方式了?
支持任意表达式
由于 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,例如有以下类:
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", "74")
>>> f"{new_comedian}"
'Eric Idle is 74.'
__str__()
方法与
__repr__()
方法用于处理对象的字符串显示方式,我们有必要至少定义其中一个。如果必须二选一的话,建议使用
__repr__()
,在
__str__()
方法没有定义的情况下,解释器会自动调用
__repr__()
方法。
__str__()
方法返回的是对象的非正式字符串表示,主要考虑可读性,而
__repr__()
方法返回的是对象的正式字符串表示,主要考虑精确性。调用这两个函数时,比较推荐的方式是直接使用内置函数
str()
和
repr()
。
f-strings 会默认调用对象的
__str__()
方法,如果要强制使用
__repr__()
方法,则可以在变量之后加上转换标志
!r
:
>>> f"{new_comedian}"'Eric Idle is 74.'
>>> f"{new_comedian!r}"
'Eric Idle is 74. Surprise!'
想要了解更多 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}.'
如果有需要,也可以通过反斜线
\
拼接多行字符串:
>>> 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.'
但在使用
"""
时,则可能出现以下情况:
>>> message = f"""
... Hi {name}.
... You are a {profession}.
... You were in {affiliation}.
... """
>>> message
'\n Hi Eric.\n You are a comedian.\n You were in Monty Python.\n'
关于代码中的缩进问题,可以参考 PEP 8 。
运行效率
f-string 中的
f
前缀也可以是 “fast” 的意思。
f-strings 比 % 和
str.format()
格式化都要快。正如之前所说,f-strings 是在运行时确定表达式的具体值的,以下是文档中的相关描述:
“F-strings 使用最简单的句法,提供了一种在字符串字面量中嵌入表达式的方式。值得注意的是,f-string 是在运行时确定表达式的值的,而不是带入一个固定值。在 Python 代码中,一个 f-string 就是一个带有
f
前缀的字符串,并通过大括号嵌入表达式,这些表达式最后将由他们的具体值取代。 (
来源
)
代码执行时,大括号中的表达式会在其命名空间中计算具体值,并返回它与其它部分的组合结果。
以下是速度比较:
>>> 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 的效率是最高的。
其实,在最初实现时,f-strings 存在一些
效率问题
,之后引入了一种特殊的操作码
BUILD_STRING
进行解决。
使用 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"The \"comedian\" is {name}, aged {age}."
'The "comedian" is Eric Idle, aged 74.'
关于字典
关于引号的问题,需要注意表达式中的字典取值。如果外部字符串使用的是单引号,字典取值就应该用双引号。
以下代码是可以工作的:
>>> comedian = {'name': 'Eric Idle', 'age': 74}
>>> f"The comedian is {comedian['name']}, aged {comedian['age']}."
The comedian is Eric Idle, aged 74.
但以下代码则会报错:
>>> comedian = {'name': 'Eric Idle', 'age': 74}
>>> 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"{{74}}"
'{74}'
注意,使用三次大括号并不会给你两个大括号:
>>> f"{{{74}}}"
'{74}'
当然,如果使用四次,就可以给你两个大括号了:
>>> f"{{{{74}}}}"
'{{74}}'
关于反斜线符号
如之前所见,我们可以在 f-strings 的字符串部分通过反斜线对一些字符进行转义。
但是,在表达式中的反斜线是没有转义效果的:
>>> f"{\"Eric Idle\"}"
File "<stdin>", line 1
f"{\"Eric Idle\"}"
SyntaxError: f-string expression part cannot include a backslash
有需要时,可以提前定义一个变量来绕过这种限制:
>>> name = "Eric Idle"