字符串格式化:百分号 vs .format vs f-string字面值

1453

有各种不同的字符串格式化方法:

  • Python <2.6: "Hello %s" % name
  • Python 2.6+: "Hello {}".format(name)   (使用str.format)
  • Python 3.6+: f"{name}"   (使用f-strings)

哪个更好,适用于哪些情况?


  1. 以下这些方法有相同的结果,那么它们之间有什么区别?

  2. name = "Alice"
    
    "Hello %s" % name
    "Hello {0}".format(name)
    f"Hello {name}"
    
    # Using named arguments:
    "Hello %(kwarg)s" % {'kwarg': name}
    "Hello {kwarg}".format(kwarg=name)
    f"Hello {name}"
    
  3. 字符串格式化是在什么时候运行的,如何避免运行时的性能损失?


如果您正在尝试关闭一个寻找字符串格式化方法的重复问题,请使用如何将变量值放入字符串中?


2
与 https://dev59.com/AHA65IYBdhLWcg3wrQp4 类似。 - carl
3
对于初学者:这是一个非常好的教程,教授了两种格式。我个人更倾向于使用旧的“%”格式,因为如果您不需要“format()”格式的改进功能,那么“%”格式通常更加方便。请点击这里查看教程:http://www.python-course.eu/python3_formatted_output.php - Lutz Prechelt
2
参考文献:Python 3文档中的新式“format()”格式化风格和旧式基于“%”的格式化风格。请查看以下链接:https://docs.python.org/3/library/string.html#format-string-syntax 和 https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting。 - Lutz Prechelt
1
回答你的第二个问题,从3.2版本开始,如果使用自定义格式化程序(参见https://docs.python.org/3/library/logging.html#logging.Formatter),则可以使用{}格式。 - yanjost
显示剩余2条评论
16个回答

987
回答你的第一个问题……. .format 似乎从多个方面来看更加先进。关于 % 的一个烦人之处是它可以接受一个变量或元组。你会认为以下情况总是可行的:
"Hello %s" % name

然而,如果 name 恰好是 (1, 2, 3),它会抛出一个 TypeError。为了确保它总是打印出来,您需要执行以下操作:

"Hello %s" % (name,)   # supply the single argument as a single-item tuple

这只是丑陋的写法。使用.format就没有这些问题。而且在你给出的第二个例子中,.format 的示例看起来更加简洁。

只有在与Python 2.5向后兼容时才应该使用它。


回答你的第二个问题,字符串格式化与任何其他操作同时发生-当字符串格式化表达式被评估时。由于Python不是一种惰性语言,在调用函数之前先评估表达式,因此表达式log.debug("some debug info: %s" % some_info)将首先评估为例如"some debug info: roflcopters are active",然后将该字符串传递给log.debug()


116
"test, test" - ted
134
请注意,在使用log.debug("something: %s", x)时不会浪费时间,但是在使用log.debug("something: %s" % x)时会浪费时间。方法中将处理字符串格式化,如果不记录日志,则不会影响性能。像往常一样,Python会满足你的需求 =) - darkfeline
68
那个做法看起来更糟糕,它和'{0}, {0}'.format('test')做的事情一样。 - flying sheep
19
关键是:新语法允许重新排列项目这一反复出现的争论毫无意义:您可以使用旧语法进行相同的操作。大多数人不知道这实际上已经在 Ansi C99 标准中定义了!查看最近的“man sprintf”副本,了解 % 占位符内部的“$”符号表示法。 - cfi
29
如果您的意思是类似于 printf("%2$d", 1, 3) 打印出 "3",那么这个在 POSIX 中有规定,而不是 C99。你引用的那个 man 页面指出:"C99 标准不包括使用 '$' 的风格...”。 - Thanatos
显示剩余12条评论

319

据我所知,取模运算符(%)无法完成以下操作:

tu = (12,45,22222,103,6)
print '{0} {2} {1} {2} {3} {2} {4} {2}'.format(*tu)

结果

12 22222 45 22222 103 22222 6 22222

非常有用。

另一个要点是:format()是一个函数,可以作为其他函数的参数使用:

li = [12,45,78,784,2,69,1254,4785,984]
print map('the number is {}'.format,li)   

print

from datetime import datetime,timedelta

once_upon_a_time = datetime(2010, 7, 1, 12, 0, 0)
delta = timedelta(days=13, hours=8,  minutes=20)

gen =(once_upon_a_time +x*delta for x in xrange(20))

print '\n'.join(map('{:%Y-%m-%d %H:%M:%S}'.format, gen))

结果为:

['the number is 12', 'the number is 45', 'the number is 78', 'the number is 784', 'the number is 2', 'the number is 69', 'the number is 1254', 'the number is 4785', 'the number is 984']

2010-07-01 12:00:00
2010-07-14 20:20:00
2010-07-28 04:40:00
2010-08-10 13:00:00
2010-08-23 21:20:00
2010-09-06 05:40:00
2010-09-19 14:00:00
2010-10-02 22:20:00
2010-10-16 06:40:00
2010-10-29 15:00:00
2010-11-11 23:20:00
2010-11-25 07:40:00
2010-12-08 16:00:00
2010-12-22 00:20:00
2011-01-04 08:40:00
2011-01-17 17:00:00
2011-01-31 01:20:00
2011-02-13 09:40:00
2011-02-26 18:00:00
2011-03-12 02:20:00

20
你可以像使用format函数一样轻松地在map中使用旧的格式化方式。示例: map('some_format_string_%s'.__mod__, some_iterable) - agf
3
请使用C99重写以上示例,以证明您是正确的。 - MarcH
11
使用gcc -std=c99 test.c -o test编译 printf("%2$s %1$s\n", "One", "Two");后,输出为Two One。但是我改正了:实际上这是 POSIX 扩展,而不是 C。我无法在 C/C++ 标准中再次找到它,尽管我之前认为它存在。即使使用'c90' std标志,该代码仍然有效。sprintf man page这个 没有列出它,但允许库实现超集。替换 'C' 为 'Posix',我的原始论点仍然有效。 - cfi
8
我的第一条评论不适用于这个答案。我对措辞感到后悔。在Python中,我们不能使用模运算符“%”来重新排列占位符。但出于评论的一致性考虑,我仍然不想删除那个第一条评论。我为在这里发泄我的愤怒而道歉。我指责经常出现的声明是旧语法本身不允许这样做。我们可以引入std Posix扩展,而不是创建全新的语法。我们都可以拥有。 - cfi
19
“模运算”是指在除法后计算余数的运算符。在这种情况下,百分号不是模运算符。 - Octopus
显示剩余5条评论

157

假设您正在使用Python的logging模块,您可以将字符串格式化参数作为参数传递给.debug()方法,而不是自己进行格式化:

log.debug("some debug info: %s", some_info)

只有在记录器实际记录了内容时,该方法才会执行格式化操作。


10
这是我刚刚学到的一些有用信息。可惜它没有自己的问题,因为它似乎与主要问题不同。遗憾的是,提问者没有将他的问题分成两个独立的问题。 - snth
13
你可以像这样使用字典格式化:log.debug("一些调试信息:%(this)s和%(that)s", dict(this='Tom', that='Jerry'))然而,即使在Python3.3中也不能在此处使用新的.format()语法,这很遗憾。 - Cito
15
@Cito:看这个:http://plumberjack.blogspot.co.uk/2010/10/supporting-alternative-formatting.html - Vinay Sajip
34
这样做的主要好处不在于性能(与记录日志后的输出相关的操作相比,执行字符串插值会很快,例如在终端中显示或保存到磁盘),而是如果您有一个日志聚合器,它可以告诉您:“您收到了此错误消息的12个实例”,即使它们都具有不同的“some_info”值。如果在将字符串传递给log.debug之前进行字符串格式化,则这是不可能的。聚合器只能说“您有12个不同的日志消息”。 - Jonathan Hartley
7
如果您关心性能,请使用字面上的 dict {} 语法,而不是 dict() 类实例化:http://doughellmann.com/2012/11/the-performance-impact-of-using-dict-instead-of-in-cpython-2-7-2.html - trojjer
显示剩余7条评论

135

7
这并没有回答问题。另一个提到f-string的答案至少谈到了性能:https://dev59.com/p2445IYBdhLWcg3wBVrz#51167833 - Georgy
f-strings 很可爱,让我想起了 Ruby 语法。但它们似乎没有太多优势,并且正如你所说,它们不必要地破坏了与 Python < 3.6 的兼容性。 - Eric Duminil
我不喜欢将表达式放入字符串中,因为这意味着代码搜索现在隐藏在字符串中,语法错误直到运行时才会被发现。我认为他们是在说f-strings不容易受到注入攻击,但我不确定是否相信他们。 - NeilG

64

PEP 3101 建议在 Python 3 中用新的高级字符串格式化来替换 % 运算符,并使其成为默认选项。


14
不正确的说法:"通过保留现有机制,可以实现向后兼容性";当然, .format 不会 替代 % 字符串格式化。 - Tobias
15
不,BrainStorms的假设是正确的:“旨在取代现有的'%'”。Tobias的引用意味着这两个系统将在一段时间内共存。RTFPEP - phobie

60

但请注意,刚才在尝试将所有%替换为.format 的现有代码中,我发现了一个问题:'{}'.format(unicode_string)将尝试对unicode_string进行编码,可能会失败。

只需查看此Python交互式会话日志:

Python 2.7.2 (default, Aug 27 2012, 19:52:55) 
[GCC 4.1.2 20080704 (Red Hat 4.1.2-48)] on linux2
; s='й'
; u=u'й'
; s
'\xd0\xb9'
; u
u'\u0439'

s 是一个字符串(在 Python3 中称为'byte array'),u 是一个Unicode字符串(在 Python3 中称为'string'):

; '%s' % s
'\xd0\xb9'
; '%s' % u
u'\u0439'

当您将Unicode对象作为参数传递给%运算符时,它将生成一个Unicode字符串,即使原始字符串不是Unicode。
; '{}'.format(s)
'\xd0\xb9'
; '{}'.format(u)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'latin-1' codec can't encode character u'\u0439' in position 0: ordinal not in range(256)

但是.format函数会引发“UnicodeEncodeError”错误:

; u'{}'.format(s)
u'\xd0\xb9'
; u'{}'.format(u)
u'\u0439'

如果原始字符串是Unicode,则只有使用Unicode参数才能正常工作。

; '{}'.format(u'i')
'i'

或者如果参数字符串可以转换为字符串(所谓的“字节数组”)


13
除非确实需要新的format方法的附加功能,否则没有理由更改正在工作的代码... - Tobias
3
例如?据我所知,这从未被需要过;我认为百分号字符串插值永远不会消失。 - Tobias
4
我认为.format()函数比%更安全,用于字符串处理。常常看到初学者犯的错误,例如"p1=%s p2=%d" % "abc", 2或者"p1=%s p2=%s" % (tuple_p1_p2,)。你可能会认为这是编码者的问题,但我认为它只是奇怪的有缺陷的语法,虽然在快速脚本中看起来不错,但对于生产代码来说是不好的。 - wobmene
4
但我不喜欢.format()的语法,我更喜欢老式的%s、%02d,比如:"p1=%s p2=%02d".format("abc", 2)。我责怪那些发明并批准花括号格式化需要使用{{}}转义,看起来很丑。 - wobmene
@rslnx所描述的问题已经不再存在了。这个问题仅适用于已经到达生命周期终点的python2.7。问题在于python2会隐式更改编码方式,而Python3将(正确地)将其报告为错误。如果你真的需要一个编码,你可以使用'foo'.encode('utf-8', errors='ignore')进行选择。 - ninMonkey
显示剩余3条评论

47

%的性能比我测试中的format要好。

测试代码:

Python 2.7.2:

import timeit
print 'format:', timeit.timeit("'{}{}{}'.format(1, 1.23, 'hello')")
print '%:', timeit.timeit("'%s%s%s' % (1, 1.23, 'hello')")

结果:

> format: 0.470329046249
> %: 0.357107877731

Python 3.5.2

import timeit
print('format:', timeit.timeit("'{}{}{}'.format(1, 1.23, 'hello')"))
print('%:', timeit.timeit("'%s%s%s' % (1, 1.23, 'hello')"))

结果

> format: 0.5864730989560485
> %: 0.013593495357781649

看起来在Python2中,差别很小,而在Python3中,%format快得多。

感谢@Chris Cogdon提供示例代码。

编辑1:

2019年7月再次在Python 3.7.2中进行了测试。

结果:

> format: 0.86600608
> %: 0.630180146

没有太大区别。我猜Python正在逐渐改进。

Edit 2:

在有人在评论中提到了Python 3的f-string之后,我在Python 3.7.2下对以下代码进行了测试:

import timeit
print('format:', timeit.timeit("'{}{}{}'.format(1, 1.23, 'hello')"))
print('%:', timeit.timeit("'%s%s%s' % (1, 1.23, 'hello')"))
print('f-string:', timeit.timeit("f'{1}{1.23}{\"hello\"}'"))

结果:

format: 0.8331376779999999
%: 0.6314778750000001
f-string: 0.766649943

似乎在性能上,f-string仍比%慢但优于format


45
相比之下,str.format 提供了更多的功能(特别是类型专用的格式化,例如 '{0:%Y-%m-%d}'.format(datetime.datetime.utcnow()))。性能并非所有工作的绝对要求。使用正确的工具来完成任务。 - minhee
37
唐纳德·克努斯曾说过:"过早优化是万恶之源"。 - Yatharth Agarwal
24
坚持使用一个众所周知的格式方案(只要它满足绝大多数情况下的需求,就可以比其他方案快两倍)不是“过早优化”,而是合理的。顺便说一句,% 运算符允许重用 printf 知识;字典插值是该原则的一个非常简单的扩展。 - Tobias
2
根据我的测试结果,Python3 和 Python 2.7 之间也存在巨大的差异。在 Python 3 中,百分号符号(%)比 format() 函数更高效。我使用的代码可以在以下链接中找到:https://github.com/rasbt/python_efficiency_tweaks/blob/master/test_code/string_subst_3.py 和 https://github.com/rasbt/python_efficiency_tweaks/blob/master/test_code/string_subst_2.py - user2489252
6
我在某种情况下实际上体验到了相反的情况。新式格式化更快。你能提供你使用的测试代码吗? - David Sanders
显示剩余5条评论

39

.format的另一个优点(我在回答中没有看到):它可以使用对象属性。

In [12]: class A(object):
   ....:     def __init__(self, x, y):
   ....:         self.x = x
   ....:         self.y = y
   ....:         

In [13]: a = A(2,3)

In [14]: 'x is {0.x}, y is {0.y}'.format(a)
Out[14]: 'x is 2, y is 3'

或者,作为关键字参数:

In [15]: 'x is {a.x}, y is {a.y}'.format(a=a)
Out[15]: 'x is 2, y is 3'

据我所知,使用%无法实现这一点。


5
与等效的'x is {0}, y is {1}'.format(a.x, a.y)相比,这个看起来更加难以阅读。只有在a.x操作非常昂贵时才应使用它。 - dtheodor
13
使用关键字参数代替位置参数,可以对代码进行微调... 'x is {a.x}, y is {a.y}'.format(a=a)。这比两个示例都更易读。 - CivFan
1
如果你有多个对象,则可以使用以下代码:'x is {a.x}, y is {a.y}'.format(**vars()) - Jacklynn
1
同样注意以相同的方式处理这个:'{foo[bar]}'.format(foo={'bar': 'baz'}) - Antoine Pinsard
5
这对于面向客户的应用程序非常有用,在这种情况下,你的应用程序提供一个标准的格式选项集与用户提供的格式字符串。我经常使用它。例如,配置文件将有一些“messagestring”属性,用户可以通过Your order, number {order[number]} was processed at {now:%Y-%m-%d %H:%M:%S}, will be ready at about {order[eta]:%H:%M:%S}或任何他们希望的方式来提供。这比尝试使用旧的格式化器提供相同的功能要清晰得多。它使用户提供的格式字符串更加强大。 - Taywee

33

我今天发现,通过使用%的旧方式格式化字符串不支持Decimal,Python的十进制定点和浮点算术模块,需要进行额外处理。

示例(使用Python 3.3.5):

#!/usr/bin/env python3

from decimal import *

getcontext().prec = 50
d = Decimal('3.12375239e-24') # no magic number, I rather produced it by banging my head on my keyboard

print('%.50f' % d)
print('{0:.50f}'.format(d))

输出:

0.00000000000000000000000312375239000000009907464850 0.00000000000000000000000312375239000000000000000000

肯定有解决方法,但您仍然可以立即考虑使用format()方法。


1
这可能是因为新式格式化在展开参数之前调用了str(d),而旧式格式化可能首先调用了float(d) - David Sanders
4
虽然你可能认为如此,但str(d)返回的是"3.12375239e-24"而不是"0.00000000000000000000000312375239000000000000000000" - Jacklynn

28

如果你的Python版本大于等于3.6,F-string格式化字符串就是你的新朋友。

它更简单、更清晰,并且性能更好。

In [1]: params=['Hello', 'adam', 42]

In [2]: %timeit "%s %s, the answer to everything is %d."%(params[0],params[1],params[2])
448 ns ± 1.48 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [3]: %timeit "{} {}, the answer to everything is {}.".format(*params)
449 ns ± 1.42 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [4]: %timeit f"{params[0]} {params[1]}, the answer to everything is {params[2]}."
12.7 ns ± 0.0129 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)

1
从Python 3.11开始,C风格的格式化(使用%s,%a和%r)与相应的f-string表达式一样快速 - TheFungusAmongUs

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