高级字符串格式化与模板字符串

65

2
这是一个很难使用模板字符串完成的 https://dev59.com/NGXWa4cB1Zd3GeqPROBJ - John La Rooy
2
好问题(+1)。有趣的是,我之前从未认真阅读过模板字符串文档中的这部分内容... - mgilson
6个回答

25

模板旨在比通常的字符串格式化更简单,代价是表达能力较弱。 PEP 292的原理比较了模板和Python的%风格的字符串格式化:

目前Python支持基于C的printf() '%'格式化字符的字符串替换语法。虽然非常丰富, 但是即使是有经验的Python程序员也容易出错。一个常见的错误是遗漏尾部格式字符,例如在%(name)s中遗漏s

此外,%后面可以跟随什么规则相当复杂,而通常应用程序很少需要这种复杂性。 大多数脚本需要进行一些字符串插值,但是其中大部分使用简单的格式化格式,即%s%(name)s。 这种形式应该变得更简单且不易出错。

尽管新的.format()改善了情况,但仍然存在格式字符串语法相当复杂的问题。因此,PEP 292的原理仍然有其优点。


2
那么字符串模板的唯一优点就是它更简单吗? - P3trus
7
基本上是的。有时不同的元字符也可能适用于不同的情况。比如,如果你在文件中编写一小段C代码并替换其中的一些字符串,模板字符串可能更可取,因为你不需要将所有花括号都加倍以转义它们,并且因为在C中"$"没有任何特殊含义。 - Sven Marnach
7
模板字符串可能更加安全。https://realpython.com/python-string-formatting/#4-template-strings-standard-library - Chris_Rands

23

字符串模板的一个关键优势是,您可以使用safe_substitute 方法仅替换某些占位符。如果未传递值给占位符,则普通格式化字符串将引发错误。例如:

"Hello, {first} {last}".format(first='Joe')

引起:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'last'

但是:

from string import Template
Template("Hello, $first $last").safe_substitute(first='Joe')

输出:

'Hello, Joe $last'

请注意,返回的值是一个字符串,而不是Template对象;如果您想要替换$last,则需要从该字符串创建一个新的Template对象。


9
你确定这是一个优势吗? - mcepl
Template("Hello, $first $last").safe_substitute(first='Joe') 什么也不会输出,你需要打印它。 :) - Apostolos
看不出这有什么优势。当然最好处理异常吧? - jonathan
6
在需要执行两层模板化的情况下,这可能是一个优势。也就是说,当我们想从另一个模板字符串生成一个模板字符串,并将剩余的替换推迟到第二个模板时,这种情况就会出现。我最近在代码审查中遇到了这样的情况,有人有理由逐步执行模板替换。 - Regorsmitz
2
非常有用,用于为模板化的正则表达式模式提供支持,其中使用了 {variant_text}_\d{4} 类型的语法,因为通常的 str.format() 函数需要为 '4' 提供一个值。这解决了这个问题。如果可以的话,我会给它 10 个赞。 - S3DEV

18

就性价比而言,使用字典进行模板替换似乎比格式化替换慢4到10倍,具体取决于模板的长度。以下是我在装有Python 3.5的2.3 GHz Core i7上在OS X下运行的快速比较。

from string import Template
lorem = "Lorem ipsum dolor sit amet {GIBBERISH}, consectetur adipiscing elit {DRIVEL}. Expectoque quid ad id, quod quaerebam, respondeas."
loremtpl = Template("Lorem ipsum dolor sit amet $GIBBERISH, consectetur adipiscing elit $DRIVEL. Expectoque quid ad id, quod quaerebam, respondeas.")
d = dict(GIBBERISH='FOOBAR', DRIVEL = 'RAXOOP')

In [29]: timeit lorem.format(**d)
1.07 µs ± 2.13 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [30]: timeit loremtpl.substitute(d)
8.74 µs ± 12.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

我测试的最坏情况是对于一个13个字符的字符串,速度慢了大约10倍。我测试的最好情况是对于一个71000个字符的字符串,速度慢了大约4倍。


我认为这只是一个打字错误,但是In[29]有1000000个循环,而In[30]只有100000个循环。请确认一下。 - sandeep
3
@sandeep 这不是打字错误。ipython的timeit命令很聪明。它只运行所需的循环次数以获得准确的估计值。模板版本大约慢一个数量级,因此timeit需要相应地减少循环次数才能达到结果。 - Mike Ellis

3

这主要是一种语法偏好的问题,通常涉及到懒惰/冗长的权衡以及对现有字符串模板系统的熟悉程度和习惯。在这种情况下,模板字符串更加懒惰/简单/快速编写,而.format()则更冗长且功能丰富。

熟悉PHP语言或Jinja系列模板系统的程序员可能更喜欢使用模板字符串。使用“%s”定位样式元组替换可能会吸引那些使用类似于printf的字符串格式化或需要快速完成任务的人。.format()具有更多功能,但除非您需要仅由.format()提供的特定功能,否则使用任何现有方案都没有问题。

唯一需要注意的是,命名字符串模板比顺序相关的模板更灵活,需要更少的维护。除此之外,所有的一切都归结为个人偏好或您所在项目的编码标准;


1
@P3trus,你误解了我的意思。我是说虽然这两种方法都提供了有序和命名字段作为一个特性,但无论你使用哪种模板API,命名字段更加灵活和稳健,这是对于这个共同特性的价值判断,而不是对这些方法本身的评价。 - Preet Kukreti
我无法看出模板何时比format()更简洁,例如string.Template('bob is $age years old').substitute(age=age)'bob is {age} years old'.format(age) - danio

2
这虽然是旧信息,但值得一提的是使用模板字符串的一个巨大优势是当您接受来自不信任来源的模板时它是安全的。例如,这可以来自配置文件。

以下是来自本文的一个示例:

CONFIG = {"SECRET_KEY": "super secret key"}

class Event:
    def __init__(self, id_, level, message):
        self.id_ = id_
        self.level = level
        self.message = message

def format_event(format_string, event):
    return format_string.format(event=event)


# user supplied template
inp = "{event.__init__.__globals__[CONFIG][SECRET_KEY]}"

event = Event(1234, "foo", "boo")
print(format_event(inp, event))

输出:

super secret key

1
补充其他答案:模板字符串与格式化字符串相比的一个优点是,通常情况下,字符{}更有可能自然地出现在一段文本中,而不是$foo${foo}(例如在生成源代码时)。这意味着使用模板时,您需要花费更少的精力来转义这些适当文本的出现。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接