对于一种语言,我们主张关注于用一种合适的方法来处理大部分的情况,Python字符串格式化却是一个另类,而且其越来越多样化。从 Python 3.6 开始我们有三种字符串格式化的方式(除了简单的连接或使用 string.Template):
- 使用%操作符
- str.format
- 插入字符串
(如果你不想主动阅读所有的这些,我会在PyGraz meetup in February 2016里给你一个关于这个稍微扩展的闪电式演讲,里面会有更多一点的例子)
%-formatting
%-formatting从1.0版本开始就成为语言的一部分了。如果你用过Python3之前的版本,你会知道这个。
1
|
"%s %s"
%
(
'Hello'
,
'World'
,
)
|
这个或多或少的有点像C语言的 sprintf
。它会起作用,但是使用起来有点复杂。
因为它只支持有限的类型,所以在传值到字符串格式化器之前,你得把你的自定义对象转换成它支持的类型之一。
很多年后,本地字符串数据类型扩展了一种 format
方法:
str.format()
在2008年10月被添加进Python2.6,类似于上下文管理器。在PEP-3101有详细描述,它着力于解决老的二元操作符%的一些不足,比如只支持有限的类型,以及在实际处理整个表达式右半部分的时候,会出现一些容易导致错误特殊的情况。
1
2
3
4
|
>>>
"%s"
%
(
"lala"
,
)
'lala'
>>>
"%s"
%
"lala"
'lala'
|
由于 .format
是一个方法而不是操作符(被映射到一个二元函数),处理参数会更加明确。如果你传入一个字符串,它就会被解释为一个字符串。如果你传入只包含一个字符串的元组,那它就被解释为只包含一个字符串的元组。
1
2
3
4
|
>>>
"{}"
.
format
(
"lala"
)
'lala'
>>>
"{}"
.
format
(
(
"lala"
,
)
)
"('lala',)"
|
对比与%操作符,它还支持使用命名参数而不需要字典。
1
|
"{firstname} {lastname}"
.
format
(
firstname
=
"Horst"
,
lastname
=
"Gutmann"
)
|
起初,它的发明是为了完全取代%操作符(它被计划用来反对Python 3.1的老式格式化功能)但是并没有完全发生。这个字符串格式化器的核心功能基本是和老的%操作符一样的,但语法略有不同,恕我直言更为直观一些。实际上,因为Ulrich和我创建了pyformat.info来帮助人们迁移到新系统。
但是,很显然,pep – 3101并没有停留在只是清理旧的特性。它还引入了一个协议,允许使用有更多样化交互的自定义类:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class
Country
:
def
__init__
(
self
,
name
,
iso
)
:
self
.
name
,
self
.
iso
=
name
,
iso
def
__format__
(
self
,
spec
)
:
if
spec
==
'short'
:
return
self
.
iso
return
self
.
name
country
=
Country
(
"Austria"
,
"AUT"
)
print
(
"{}"
.
format
(
country
)
)
print
(
"{:short}"
.
format
(
country
)
)
|
你可以想象对于字符串格式化 __format__
方法像 __str__
一样你可以传递选择项。如果你在你的对象中有 __format__
方法,当你使用格式化方法时它就会代替 __str__
(除非你做些什么比如 "{!s}".format(country)"
)。
事实上你会在 datetime.date class in Python 3.4找到一个关于如何使用的很好的例子:
1
2
3
4
5
6
|
class
date
:
.
.
.
def
__format__
(
self
,
fmt
)
:
if
len
(
fmt
)
!=
0
:
return
self
.
strftime
(
fmt
)
return
str
(
self
)
|
这允许你使用“parent”字符串格式直接来格式化日期,这样就不需要首先把你的日期对象转换成一个字符串,然后再传递到字符串格式化器中:
1
2
3
|
import
datetime
print
(
"Today is {:%A}"
.
format
(
datetime
.
datetime
.
now
(
)
)
)
# Today is Thursday
|
PEP-0498: 字符串插入
这是现在推荐的字符串格式化方法,.format
太繁琐了:
1
2
3
4
5
|
a
=
"Hello"
b
=
"World"
"{} {}"
.
format
(
a
,
b
)
# vs.
"%s %s"
%
(
a
,
b
,
)
|
PEP-0498致力于通过提供一些在别的语言比如Ruby,Scala,Perl中常见且存在一段时间的东西:字符串插入,来改善这种情况。这里表达式可以直接被集成到字符串,这意味着你不必再显式地调用任何额外的函数。
ES2015最近将这个特性引入到了JavaScript中,它被称为“模板字符串”:
1
2
|
const
username
=
"Horst"
;
const
welcomeMsg
=
`
Hello
,
$
{
username
}
!
`
;
|
因为Python 3.0的一点历史问题,引号在Python中并不可用。再次引入也会再次影响到语言的基本语法。相代替的,另一个字符串前缀被引入: f
.
1
2
3
4
|
a
=
"Hello"
b
=
"World"
f
"{a} {b}"
f
"{a + ' ' + b}"
|
你不再需要对一个字符串显式地调用 .format()
方法了,只是简单地用 f
前缀标记一下格式以及内联最终字符串中你想要包括的表达式。否则它们就会提供和 .format()
相同的功能。这些格式化字符串在文档中也被称为“f-strings”。
这看起来确实十分美好,但因为python 3.6会在12个月后才能够发布,你恐怕还要等上一段时间。话虽这么说,代码却已经在那儿了,所以你能做的就是获取一个python 3.6预发布版本或者使用一些像 pyenv 的小诀窍,然后让它运行就行了。
其实还有很多,这里有其他的PEP(0501),它想要引入i-strings,而这类字符串导致了字符串的懒惰计算,以至于例如你能在最终评估之前能做一些国际化(i18n)或者安全检查。虽然这个提案已延缓至进一步的讨论,但这看起来是个很好的想法。
但是回到f-strings:如果你想知道更多关于字符串插入的解决方案,看看PEP-0502 ,它包括一个关于背后的动机和来自其他语言特性灵感的详细讨论。