对于一种语言,我们主张关注于用一种合适的方法来处理大部分的情况,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 ,它包括一个关于背后的动机和来自其他语言特性灵感的详细讨论。