分享

Python字符串格式化问题:%、format()与f-strings

 黄沙留梦 2019-11-07

Python字符串格式化问题:%、format()与f-strings

本文为译文,原文 by Joanna Jablonski,地址:https:///python-f-strings/

Python 3.6 引入了新的字符串格式化方式 f-strings,与其它格式化方式相比,不仅简洁明了,可读性更好,更不容易出错,而且运行效率也更高。

你应该马上开始使用这种新的格式化方式,本文将解释其原因与具体用法。

但在此之前,让我们先看一下 f-strings 之前的字符串格式化方式。


1. Python 字符串格式化的“旧式”做法

在 Python 3.6 之前,把 Python 表达式嵌入字符串,主要有两种方式:% 或 str.format()。它们的用法与主要不足如下:

1.1 选项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.'

因此,这种格式化方式的主要问题是过于繁琐,容易出错,不能正确格式化元组与字典。


1.2 选项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.'

当然,如果我们提前把需要嵌入的变量放在一个字典中,就可以通过 ** 进行拆包操作,从而显得简洁一些。

但一定还存在着更好的格式化方式。


2. f-Strings:一种改进版格式化方式

Python 3.6 引入了新的字符串格式化方式,这种方式来自于 Eric V. Smith 在 2015 年 8 月提出的方案,具体可以参考PEP 498。

f-strings 也称作“格式化的字符串字面量”,它是一个带有 f 前缀的字符串,通过大括号嵌入所需的 Python 表达式,这些表达式的具体值是在运行时确定的,背后依赖的也是嵌入对象的 __format()__ 接口。查看 官方文档 可以获得更多信息。

以下是一些具体使用方式:

2.1 最简单的句法

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.'

你是不是已经开始喜欢上这种格式化方式了?

2.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,例如有以下类:

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 的转换问题,可以阅读 相关资料。

2.3 多行字符串中使用 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。

2.4 运行效率

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进行解决。


3. 使用 f-Strings 的一些小细节

现在,我们已经了解了 f-strings 的优势,相信大家都已经准备开始使用了。以下是一些需要注意的小细节。

3.1 关于引号

我们可以在表达式中使用各种引号,但应该注意,表达式中的引号不要与外部的引号一样。

以下代码是正确的:

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

以下代码也可以运行:

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

我们也可以使用三引号:

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

如果确实要在字符串中使用与外部一样的引号的话,可以使用反斜线\进行转义:

3.2 关于字典

关于引号的问题,需要注意表达式中的字典取值。如果外部字符串使用的是单引号,字典取值就应该用双引号。

以下代码是可以工作的:

>>> 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 '', line 1 f'The comedian is {comedian['name']}, aged {comedian['age']}.' ^SyntaxError: invalid syntax

如果在内部表达式中使用同样的引号,解释器会将其判断为字符串的结束符号。

3.3 关于大括号

如果想在表达式中使用大括号,我们必须使用两次大括号:

>>> f'{{74}}''{74}'

注意,使用三次大括号并不会给你两个大括号:

>>> f'{{{74}}}''{74}'

当然,如果使用四次,就可以给你两个大括号了:

>>> f'{{{{74}}}}''{{74}}'

3.4 关于反斜线符号

如之前所见,我们可以在 f-strings 的字符串部分通过反斜线对一些字符进行转义。

但是,在表达式中的反斜线是没有转义效果的:

>>> f'{\'Eric Idle\'}' File '', line 1 f'{\'Eric Idle\'}' ^SyntaxError: f-string expression part cannot include a backslash

有需要时,可以提前定义一个变量来绕过这种限制:

>>> name = 'Eric Idle'>>> f'{name}''Eric Idle'

3.5 关于行内注释

f-strings 中不应包括带 # 号的注释,否则会导致句法错误:

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

4. 开始使用吧

你依然可以使用原来的字符串格式化方式,但 f-strings 提供了一种更加简洁,更具可读性,更方便的格式化方式,不仅运行更快,也更不容易出错。因此,如果你还没切换到 3.6 或更高的版本的话,f-strings 或许是一个值得考虑的切换理由。(如果你还在使用 Python 2,别忘了 2020年 就快到了!)

根据 Python之禅,当你需要做一件事的时候,“总有一种——最好是只有一种——最为明显的方式。” f-strings 或许不是字符串格式化的唯一方式,但很可能是最为明显的方式。


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多