有多种字符串格式化方法:
Python <2.6:“你好 %s”% 名称
Python 2.6+:“Hello {}”.format(name)(使用 str.format)
Python 3.6+:f"{name}"(使用 f 字符串)
哪个更好,在什么情况下?
以下方法具有相同的结果,那么有什么区别? name = "Alice" "Hello %s" % name "Hello {0}".format(name) f"Hello {name}" # 使用命名参数:"Hello %(kwarg)s" % {'kwarg': name } "Hello {kwarg}".format(kwarg=name) f"Hello {name}" 字符串格式化何时运行,如何避免运行时性能损失?
format()
formatting style 和 older %
-based formatting style 的 Python 3 文档。
要回答您的第一个问题... .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()
。
模运算符( % )不能做的事情,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: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
map
中使用旧样式格式,就像使用格式一样容易。 map('some_format_string_%s'.__mod__, some_iterable)
gcc -std=c99 test.c -o test
编译的 printf("%2$s %1$s\n", "One", "Two");
,输出为 Two One
。但我的立场是正确的:It is actually a POSIX extension 而不是 C。我无法在 C/C++ 标准中再次找到它,我以为我已经看到它了。该代码甚至可以使用 'c90' std 标志。 sprintf
man page。 This 没有列出它,但允许库实现超集。我原来的论点仍然有效,将 C
替换为 Posix
%
来重新排序占位符。为了这里的评论一致性,我仍然不想删除第一条评论。我很抱歉在这里发泄了我的愤怒。它针对的是旧语法本身不允许这样做的经常声明。我们可以引入 std Posix 扩展,而不是创建一个全新的语法。我们可以两者兼得。
假设您使用 Python 的 logging
模块,您可以将字符串格式化参数作为参数传递给 .debug()
方法,而不是自己进行格式化:
log.debug("some debug info: %s", some_info)
除非记录器实际记录某些内容,否则可以避免进行格式化。
log.debug("some debug info: %(this)s and %(that)s", dict(this='Tom', that='Jerry'))
但是,您不能在此处使用新样式的 .format()
语法,即使在 Python 3.3 中也不能,这是一种耻辱。
从 Python 3.6 (2016) 开始,您可以使用 f-strings 替换变量:
>>> 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
PEP 3101 建议用 Python 3 中新的高级字符串格式替换 %
运算符,这将是默认值。
.format
不会替换 %
字符串格式。
但是请注意,刚才我在尝试将现有代码中的所有 %
替换为 .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 中称为“字节数组”),而 u
是一个 Unicode 字符串(在 Python3 中称为“字符串”):
; '%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'
或者如果参数字符串可以转换为字符串(所谓的“字节数组”)
format
方法的附加功能,否则根本没有理由更改工作代码......
%
字符串插值不太可能消失。
"p1=%s p2=%d" % "abc", 2
或 "p1=%s p2=%s" % (tuple_p1_p2,)
这样的初学者错误。您可能认为这是编码员的错,但我认为这只是奇怪的错误语法,对于快速脚本来说看起来不错,但对生产代码不利。
%s
、%02d
之类的 "p1=%s p2=%02d".format("abc", 2)
。我责怪那些发明和批准花括号格式的人,这些格式需要你像 {{}}
一样逃避它们,而且看起来很丑恕我直言。
根据我的测试,%
的性能优于 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 正在逐渐改进。
编辑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
好。
str.format
提供了更多功能(尤其是类型专用格式,例如 '{0:%Y-%m-%d}'.format(datetime.datetime.utcnow())
)。绩效不能是所有工作的绝对要求。为工作使用正确的工具。
%
运算符允许重用 printf
知识;字典插值是原理的一个非常简单的扩展。
%
在 Python 3 中比 format()
高效得多。我使用的代码可以在这里找到:github.com/rasbt/python_efficiency_tweaks/blob/master/test_code/… 和 github.com/rasbt/python_efficiency_tweaks/blob/master/test_code/…
.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'
据我所知,这对于 %
是不可能的。
'x is {0}, y is {1}'.format(a.x, a.y)
相比,这看起来比必要的更不可读。仅应在 a.x
操作非常昂贵时使用。
'x is {a.x}, y is {a.y}'.format(a=a)
。比这两个例子更具可读性。
'x is {a.x}, y is {a.y}'.format(**vars())
'{foo[bar]}'.format(foo={'bar': 'baz'})
。
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}
或他们希望的任何内容提供该属性。这比尝试使用旧格式化程序提供相同的功能要干净得多。它使用户提供的格式字符串更加强大。
正如我今天发现的那样,通过 %
格式化字符串的旧方法不支持 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.000000000000000000000000312375239000000009907464850 0.00000000000000000000000312375239000000000000000000
当然可能有变通办法,但您仍然可以考虑立即使用 format()
方法。
str(d)
,而旧式格式可能会先调用 float(d)
。
str(d)
返回 "3.12375239e-24"
,而不是 "0.00000000000000000000000312375239000000000000000000"
如果您的 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)
附带说明一下,您不必对性能造成影响就可以使用带有日志记录的新样式格式。您可以将任何对象传递给实现 __str__
魔术方法的 logging.debug
、logging.info
等。当日志记录模块决定它必须发出您的消息对象(无论它是什么)时,它会在此之前调用 str(message_object)
。所以你可以做这样的事情:
import logging
class NewStyleLogMessage(object):
def __init__(self, message, *args, **kwargs):
self.message = message
self.args = args
self.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 log
logging.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 log
logging.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
进行日志记录 - 通过完全按照 logging
的设计目的覆盖 __str__
来实现 - 将函数调用缩短为单个字母 (N
),这感觉与某些函数非常相似定义字符串的标准方法——AND 允许惰性函数调用。谢谢! +1
logging.Formatter(style='{')
参数的结果有什么不同吗?
%
可能有帮助的一种情况是您正在格式化正则表达式。例如,
'{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}}'
。当您有两个正则表达式时,这可能很有用,其中一个单独使用而没有格式,但两者的连接已格式化。
'{type_names} [a-z]{.format()
}'.format(type_names='triangle|square')
。这就像说 .format()
在使用已经包含百分比字符的字符串时会有所帮助。当然。然后你必须逃离他们。
"One situation where % may help is when you are formatting regex expressions."
开头的原因具体来说,假设 a=r"[a-z]{2}"
是一个正则表达式块,您将在两个不同的最终表达式(例如 c1 = b + a
和 c2 = a
)中使用它。假设 c1
需要被 format
编辑(例如 b
需要在运行时格式化),但 c2
不需要。然后,对于 c2
,您需要 a=r"[a-z]{2}"
,对于 c1.format(...)
,您需要 a=r"[a-z]{{2}}"
。
我要补充一点,从 3.6 版开始,我们可以使用 fstrings,如下所示
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.7 对比:
#!/usr/bin/env python
import 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_it
def new_new_format(s):
print("new_new_format:", f"{s[0]} {s[1]} {s[2]} {s[3]} {s[4]}")
@time_it
def new_format(s):
print("new_format:", "{0} {1} {2} {3} {4}".format(*s))
@time_it
def 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 cinco
0.0000170280 seconds
new_format: uno dos tres cuatro cinco
0.0000046750 seconds
old_format: uno dos tres cuatro cinco
0.0000034820 seconds
-----
new_new_format: 1 2 3 4 5
0.0000043980 seconds
new_format: 1 2 3 4 5
0.0000062590 seconds
old_format: 1 2 3 4 5
0.0000041730 seconds
-----
new_new_format: 1.1 2.1 3.1 4.1 5.1
0.0000092650 seconds
new_format: 1.1 2.1 3.1 4.1 5.1
0.0000055340 seconds
old_format: 1.1 2.1 3.1 4.1 5.1
0.0000052130 seconds
-----
new_new_format: uno 2 3.14 cuatro 5.5
0.0000053380 seconds
new_format: uno 2 3.14 cuatro 5.5
0.0000047570 seconds
old_format: uno 2 3.14 cuatro 5.5
0.0000045320 seconds
-----
但有一件事是,如果您有嵌套的花括号,则不适用于格式,但 %
将起作用。
例子:
>>> '{{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}'
>>>
不定期副业成功案例分享
"%(a)s, %(a)s" % {'a':'test'}
呢log.debug("something: %s" % x)
而不是log.debug("something: %s", x)
上浪费时间。字符串格式将在方法中处理,如果不记录,您将不会受到性能影响。与往常一样,Python 会预测您的需求 =)'{0}, {0}'.format('test')
相同的做法看起来更糟。man sprintf
副本并了解%
占位符内的$
表示法printf("%2$d", 1, 3)
打印出“3”,这是在 POSIX 中指定的,而不是 C99。您引用的手册页指出,“C99 标准不包括使用 '$'... 的样式”。