从 Python 3.6 开始,f-strings 是一种很好的格式化字符串的新方法。它们不仅比其他格式化方式更易读、更简洁、更不容易出错,而且速度也更快!

在本文结束时,您将了解如何以及为什么从今天开始使用 f-string。

Python 中的“老派”字符串格式

在 Python 3.6 之前,有两种主要方法可以将 Python 表达式嵌入到字符串文字中以进行格式化:%-formattingstr.format(). 您将看到如何使用它们以及它们的局限性。

1: %-formatting

这是 Python 格式化的 OG,从一开始就在该语言中。

您可以在 Python 文档中阅读更多内容。请记住,文档不建议使用 %-formatting,其中包含以下注释:

这里描述的格式化操作表现出各种quirks,这些quirks会导致一些常见错误(例如无法正确显示元组和字典)

使用较新的格式化字符串文字或 str.format() 接口有助于避免这些错误。 这些替代方案还为格式化文本提供了更强大、更灵活和可扩展的方法。

格式化符号 功能
%s 格式化字符串
%c 格式化字符及其ASCII码
%d 格式化十进制整数
%o 格式化八进制数
%x / %X 格式化十六进制数(x / X 代表转换后的十六进制字符的大小写)
%f 格式化浮点数字

如何使用 %-formatting

字符串对象有一个使用 % 运算符的内置操作,可以使用它来格式化字符串。

1
2
3
>>> name = "Eric"
>>> "Hello, %s." % name
'Hello, Eric.'

为了插入多个变量,必须使用这些变量的元组。

1
2
3
4
>>> name = "Eric"
>>> age = 74
>>> "Hello, %s. You are %s." % (name, age)
'Hello Eric. You are 74.'

为什么 %-formatting 不好

上面看到的代码示例具有足够的可读性。但是,一旦开始使用多个参数和更长的字符串,代码将很快变得不那么容易阅读。事情已经开始看起来有点乱了:

1
2
3
4
5
6
7
>>> 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 中引入的。可以查看较新的 Python 字符串格式技术指南以获取更多信息。

如何使用 str.format()

str.format()  是对 %-formatting 的改进。它使用正常的函数调用语法,并且可以通过正在转换为字符串的对象上的 format() 方法进行扩展

使用 str.format(),替换字段用大括号标记:

1
2
3
4
>>> name = "Eric"
>>> age = 74
>>> "Hello, {}. You are {}.".format(name, age)
'Hello, Eric. You are 74.'

可以通过引用它们的索引以任何顺序引用变量:

1
2
>>> "Hello, {1}. You are {0}.".format(age, name)
'Hello, Eric. You are 74.'

但是,如果您插入变量名称,则即可以传递对象,还可以在大括号之间引用参数和方法:

1
2
3
>>> person = {'name': 'Eric', 'age': 74}
>>> "Hello, {name}. You are {age}.".format(name=person['name'], age=person['age'])
'Hello, Eric. You are 74.'

还可以使用 ** 来使用字典来完成这个巧妙的技巧:

1
2
3
>>> person = {'name': 'Eric', 'age': 74}
>>> "Hello, {name}. You are {age}.".format(**person)
'Hello, Eric. You are 74.'

与 %-formatting 相比,str.format() 绝对是一个升级

为什么 str.format() 不好

使用 str.format() 的代码比使用 %-formatting 的代码更容易阅读,但是当您处理多个参数和较长的字符串时,str.format() 仍然可能非常冗长。看看这个:

1
2
3
4
5
6
7
8
9
10
>>> 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.'

如果你有你想在字典中传递给 .format() 的变量,那么你可以用 .format(**some_dict) 解压它并通过字符串中的键引用值,但必须有一个更好的方法来做到这一点。

f-Strings

一种在 Python 中格式化字符串的新方法和改进方法

f-string亦称为格式化字符串常量(formatted string literals

是Python3.6引入的一种字符串格式化方法,主要目的是使格式化字符串的操作更加简便。

f-string在功能方面不逊于传统的 %-formatting 语句和 str.format() 函数

同时性能又优于二者,且使用起来也更加简洁明了,因此对于Python3.6及以后的版本,推荐使用f-string进行字符串格式化。

简单语法

语法类似于使用 str.format() 的语法,但不那么冗长。

1
2
3
4
>>> name = "Eric"
>>> age = 74
>>> f"Hello, {name}. You are {age}."
'Hello, Eric. You are 74.'

使用大写字母 F 也是有效的:

1
2
>>> F"Hello, {name}. You are {age}."
'Hello, Eric. You are 74.'

任意表达式

因为 f-strings 是在运行时评估的,所以您可以将任何和所有有效的 Python 表达式放入其中。这使您可以做一些漂亮的事情。

可以做一些非常简单的事情,像这样:

1
2
>>> f"{2 * 37}"
'74'

也可以调用函数

1
2
3
4
5
6
def to_lowercase(input):
return input.lower()

name = "Eric Idle"
f"{to_lowercase(name)} is funny."

还可以选择直接调用方法:

1
2
>>> f"{name.lower()} is funny."
'eric idle is funny.'

甚至可以使用从具有 f-strings 的类创建的对象。假设有以下class:

1
2
3
4
5
6
7
8
9
10
11
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!"

你可以这样做:

1
2
3
>>> new_comedian = Comedian("Eric", "Idle", "74")
>>> f"{new_comedian}"
'Eric Idle is 74.'

__str__() __repr__() 方法处理对象如何呈现为字符串,因此您需要确保在类定义中至少包含这些方法之一。 如果您必须选择一个,请使用 __repr__(),因为它可以用来代替 __str__()

__str__() 返回的字符串是对象的非正式字符串表示形式,应该是可读的。 __repr__() 返回的字符串是官方的表示,应该是明确的。 调用 str()repr() 优于直接使用 __str__()__repr__()

默认情况下, f-strings 将使用 __str__(),但如果包含转换标志 !r,则可以确保它们使用 __repr__()

1
2
3
4
>>> f"{new_comedian}"
'Eric Idle is 74.'
>>> f"{new_comedian!r}"
'Eric Idle is 74. Surprise!'

如果您想阅读一些导致 f-strings 支持完整 Python 表达式的对话,您可以在此处进行。

多行 f-Strings

可以有多行字符串:

1
2
3
4
5
6
7
8
9
10
>>> 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。以下代码将不起作用:

1
2
3
4
5
6
7
>>> message = (
... f"Hi {name}. "
... "You are a {profession}. "
... "You were in {affiliation}."
... )
>>> message
'Hi Eric. You are a {profession}. You were in {affiliation}.'

如果想将字符串分散到多行,还可以选择使用 \ 转义返回:

1
2
3
4
5
6
>>> 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.'

但是如果你使用 """ 会发生这种情况:

1
2
3
4
5
6
7
8
>>> 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'

速度

f-strings中的 f 也可以代表“fast”。

f-strings 比 %-formatting 和 str.format() 都快。 正如您已经看到的,f 字符串是在运行时计算的表达式,而不是常量值。 这是文档的摘录:

F-strings 提供了一种在字符串文字中嵌入表达式的方法,使用最少的语法。

应该注意的是,f-string 实际上是在运行时计算的表达式,而不是常量值。

在 Python 源代码中,f-string 是一个以 f 为前缀的文字字符串,其中包含大括号内的表达式。 表达式被替换为它们的值。

在运行时,花括号内的表达式在其自己的范围内进行评估,然后与 f-string 的字符串文字部分放在一起。然后返回结果字符串。这就是全部。

速度比较:

1
2
3
4
5
>>> import timeit
>>> timeit.timeit("""name = "Eric"
... age = 74
... '%s is %s.' % (name, age)""", number = 10000)
0.003324444866599663
1
2
3
4
>>> timeit.timeit("""name = "Eric"
... age = 74
... '{} is {}.'.format(name, age)""", number = 10000)
0.004242089427570761
1
2
3
4
>>> timeit.timeit("""name = "Eric"
... age = 74
... f'{name} is {age}.'""", number = 10000)
0.0024820892040722242

从上面可以看出来 f-string 速度更快

然而,情况并非总是如此。当它们第一次实现时,它们存在一些速度问题,需要比 str.format() 更快。引入了特殊的 BUILD_STRING opcode

f-string需要注意的地方

引号

可以在表达式中使用各种类型的引号。只需确保在 f-string 外部使用的引号类型与您在表达式中使用的引号不同。

此代码将起作用:

1
2
3
4
5
6
7
8
9
10
11
12
>>> f"{'Eric Idle'}"
'Eric Idle'

>>> f'{"Eric Idle"}'
'Eric Idle'

# 还可以使用三引号:
>>> f"""Eric Idle"""
'Eric Idle'

>>> f'''Eric Idle'''
'Eric Idle'

如果你发现你需要在字符串的内部和外部使用相同类型的引号,那么你可以用 \ 转义:

1
2
>>> f"The \"comedian\" is {name}, aged {age}."
'The "comedian" is Eric Idle, aged 74.'

字典

说到引号,当你使用字典时要小心。 如果要对字典的键使用单引号,请记住确保对包含键的 f-strings 使用双引号。

1
2
3
>>> comedian = {'name': 'Eric Idle', 'age': 74}
>>> f"The comedian is {comedian['name']}, aged {comedian['age']}."
The comedian is Eric Idle, aged 74.

但这将是一个常犯语法错误:

1
2
3
4
5
6
>>> 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-string 外部使用相同类型的引号,则第一个字典键开头的引号将被解释为字符串的结尾。

大括号

为了使大括号出现在您的字符串中,您必须使用双大括号:

1
2
>>> f"{{70 + 4}}"
'{70 + 4}'

请注意,使用三重大括号将导致字符串中只有一个大括号:

1
2
>>> f"{{{70 + 4}}}"
'{74}'

但是,如果您使用三个以上的大括号,您可以获得更多的大括号:

1
2
>>> f"{{{{70 + 4}}}}"
'{{70 + 4}}'

反斜杠

正如您之前看到的,您可以在 f-string 的字符串部分使用反斜杠转义。但是,您不能在 f-string 的表达式部分使用反斜杠转义:

1
2
3
4
5
>>> f"{\"Eric Idle\"}"
File "<stdin>", line 1
f"{\"Eric Idle\"}"
^
SyntaxError: f-string expression part cannot include a backslash

您可以通过预先评估表达式并使用 f 字符串中的结果来解决此问题:

1
2
3
>>> name = "Eric Idle"
>>> f"{name}"
'Eric Idle'

行内注释

表达式不应包含使用 # 符号的注释。你会得到一个语法错误:

1
2
3
4
5
>>> f"Eric is {2 * 37 #Oh my!}."
File "<stdin>", line 1
f"Eric is {2 * 37 #Oh my!}."
^
SyntaxError: f-string expression part cannot include '#'