字符串格式:%vs…格式vs. f-string文字

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

  • Python<2.6:"Hello %s" % name
  • Python 2.6+:"Hello {}".format(name)(使用#1
  • Python 3.6+:f"{name}"(使用f字符串)

哪一种更好,适用于哪些情况?


  1. 以下方法有相同的结果,那么有什么区别?

    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}"
  2. 字符串格式化何时运行,如何避免运行时性能损失?


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

1040018 次浏览

回答你的第一个问题…….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()

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

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

这避免了格式化,除非记录器实际记录了一些东西。

从我的测试来看,%format提供了更好的性能。

测试代码:

Python 2.7.2:

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

结果:

> format: 0.470329046249> %: 0.357107877731

python3.5.2

import timeitprint('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正在逐渐改进。

编辑2:

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

import timeitprint('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.6314778750000001f-string: 0.766649943

看起来f-string仍然比%慢,但比format好。

模运算符 ( % ) 不能做的事情,afaik:

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:002010-07-14 20:20:002010-07-28 04:40:002010-08-10 13:00:002010-08-23 21:20:002010-09-06 05:40:002010-09-19 14:00:002010-10-02 22:20:002010-10-16 06:40:002010-10-29 15:00:002010-11-11 23:20:002010-11-25 07:40:002010-12-08 16:00:002010-12-22 00:20:002011-01-04 08:40:002011-01-17 17:00:002011-01-31 01:20:002011-02-13 09:40:002011-02-26 18:00:002011-03-12 02:20:00

PEP 3101建议用Python 3中新的高级字符串格式替换%运算符,这将是默认值。

但是请小心,刚才我在尝试将现有代码中的所有%替换为.format时发现了一个问题:#2将尝试编码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'; uu'\u0439'

s只是一个字符串(在Python3中称为“字节数组”),u是一个Unicode字符串(在Python3中称为“字符串”):

; '%s' % s'\xd0\xb9'; '%s' % uu'\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'

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

正如我今天发现的,通过%格式化字符串的旧方法不支持Decimal,Python的十进制定点和浮点运算模块,开箱即用。

示例(使用Python 3.3.5):

#!/usr/bin/env python3
from decimal import *
getcontext().prec = 50d = 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.000000000000000000000003123752390000000099074648500.00000000000000000000000312375239000000000000000000

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

顺便说一句,在日志记录中使用新样式格式不必降低性能。你可以将任何对象传递给logging.debuglogging.info等实现__str__魔法方法的对象。当日志模块决定它必须发出你的消息对象(无论它是什么)时,它会在这样做之前调用str(message_object)。所以你可以这样做:

import logging

class NewStyleLogMessage(object):def __init__(self, message, *args, **kwargs):self.message = messageself.args = argsself.kwargs = kwargs
def __str__(self):args = (i() if callable(i) else i for i in self.args)kwargs = dict((k, v() if callable(v) else v) for k, v in self.kwargs.items())
return self.message.format(*args, **kwargs)
N = NewStyleLogMessage
# Neither one of these messages are formatted (or calculated) until they're# needed
# Emits "Lazily formatted log entry: 123 foo" in loglogging.debug(N('Lazily formatted log entry: {0} {keyword}', 123, keyword='foo'))

def expensive_func():# Do something that takes a long time...return 'foo'
# Emits "Expensive log entry: foo" in loglogging.debug(N('Expensive log entry: {keyword}', keyword=expensive_func))

这一切都在Python 3留档(https://docs.python.org/3/howto/logging-cookbook.html#formatting-styles)中描述。但是,它也适用于Python 2.6(https://docs.python.org/2.6/library/logging.html#using-arbitrary-objects-as-messages)。

使用这种技术的优点之一,除了它与格式化风格无关之外,是它允许惰性值,例如上面的函数expensive_func。这为Python文档中给出的建议提供了更优雅的替代方案:https://docs.python.org/2.6/library/logging.html#optimization

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

据我所知,这在%中是不可能的。

%可能有帮助的一种情况是格式化regex表达式。例如,

'{type_names} [a-z]{2}'.format(type_names='triangle|square')

提升IndexError。在这种情况下,您可以使用:

'%(type_names)s [a-z]{2}' % {'type_names': 'triangle|square'}

这避免了将正则表达式写入'{type_names} [a-z]\{\{2}}'。当您有两个正则表达式时,这可能很有用,其中一个单独使用而没有格式,但两者的连接是格式化的。

从Python 3.6(2016)开始,您可以使用f弦来替换变量:

>>> origin = "London">>> destination = "Paris">>> f"from {origin} to {destination}"'from London to Paris'

注意f"前缀。如果您在Python 3.5或更早版本中尝试此操作,您将获得SyntaxError

https://docs.python.org/3.6/reference/lexical_analysis.html#f-strings

对于python版本>=3.6(见PEP 498

s1='albha's2='beta'
f'{s1}{s2:>10}'
#output'albha      beta'

我想补充一点,从3.6版开始,我们可以像下面这样使用fstring

foo = "john"bar = "smith"print(f"My name is {foo} {bar}")

这给了

我叫约翰·史密斯

一切都转换成字符串

mylist = ["foo", "bar"]print(f"mylist = {mylist}")

结果:

mylist=['foo','bar']//列表页

您可以传递函数,就像在其他格式方法中一样

print(f'Hello, here is the date : {time.strftime("%d/%m/%Y")}')

举个例子

您好,这里是日期:16/04/2018

如果你的python>=3.6,F字符串格式的文字是你的新朋友。

它更简单,干净,性能更好。

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)

但有一点是,如果你有嵌套的花括号,它不适用于格式,但%可以。

示例:

>>> '\{\{0}, {1}}'.format(1,2)Traceback (most recent call last):File "<pyshell#3>", line 1, in <module>'\{\{0}, {1}}'.format(1,2)ValueError: Single '}' encountered in format string>>> '{%s, %s}'%(1,2)'{1, 2}'>>>

Python 3.6.7比较:

#!/usr/bin/env pythonimport timeit
def time_it(fn):"""Measure time of execution of a function"""def wrapper(*args, **kwargs):t0 = timeit.default_timer()fn(*args, **kwargs)t1 = timeit.default_timer()print("{0:.10f} seconds".format(t1 - t0))return wrapper

@time_itdef new_new_format(s):print("new_new_format:", f"{s[0]} {s[1]} {s[2]} {s[3]} {s[4]}")

@time_itdef new_format(s):print("new_format:", "{0} {1} {2} {3} {4}".format(*s))

@time_itdef old_format(s):print("old_format:", "%s %s %s %s %s" % s)

def main():samples = (("uno", "dos", "tres", "cuatro", "cinco"), (1,2,3,4,5), (1.1, 2.1, 3.1, 4.1, 5.1), ("uno", 2, 3.14, "cuatro", 5.5),)for s in samples:new_new_format(s)new_format(s)old_format(s)print("-----")

if __name__ == '__main__':main()

输出:

new_new_format: uno dos tres cuatro cinco0.0000170280 secondsnew_format: uno dos tres cuatro cinco0.0000046750 secondsold_format: uno dos tres cuatro cinco0.0000034820 seconds-----new_new_format: 1 2 3 4 50.0000043980 secondsnew_format: 1 2 3 4 50.0000062590 secondsold_format: 1 2 3 4 50.0000041730 seconds-----new_new_format: 1.1 2.1 3.1 4.1 5.10.0000092650 secondsnew_format: 1.1 2.1 3.1 4.1 5.10.0000055340 secondsold_format: 1.1 2.1 3.1 4.1 5.10.0000052130 seconds-----new_new_format: uno 2 3.14 cuatro 5.50.0000053380 secondsnew_format: uno 2 3.14 cuatro 5.50.0000047570 secondsold_format: uno 2 3.14 cuatro 5.50.0000045320 seconds-----