字符串格式:%vs. .format

时间:2011-02-22 18:46:42

标签: python performance logging string-formatting

Python 2.6引入了str.format()方法,其语法与现有%运算符略有不同。哪种情况更好,哪种情况更好?

  1. 以下使用每种方法并且结果相同,那么区别是什么?

    #!/usr/bin/python
    sub1 = "python string!"
    sub2 = "an arg"
    
    a = "i am a %s" % sub1
    b = "i am a {0}".format(sub1)
    
    c = "with %(kwarg)s!" % {'kwarg':sub2}
    d = "with {kwarg}!".format(kwarg=sub2)
    
    print a    # "i am a python string!"
    print b    # "i am a python string!"
    print c    # "with an arg!"
    print d    # "with an arg!"
    
  2. 此外,何时在Python中发生字符串格式化?例如,如果我的日志记录级别设置为HIGH,我仍然会执行以下%操作吗?如果是这样,有没有办法避免这种情况?

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

17 个答案:

答案 0 :(得分:905)

要回答你的第一个问题...... .format在许多方面似乎更为复杂。关于%的一个令人讨厌的事情也是它如何采用变量或元组。您认为以下内容始终有效:

"hi there %s" % name

然而,如果name恰好是(1, 2, 3),它会抛出TypeError。为了保证它始终打印,你需要做

"hi there %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()

答案 1 :(得分:293)

模数运算符(%)无法做的事情,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

答案 2 :(得分:135)

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

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

除非记录器实际记录了某些内容,否则会避免进行格式化。

答案 3 :(得分:105)

As of Python 3.6 (2016) you can use f-strings to substitute variables:

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

Note the f" prefix. If you try this in Python 3.5 or earlier, you'll get a SyntaxError.

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

答案 4 :(得分:56)

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

答案 5 :(得分:51)

但是请注意,刚才我在尝试用现有代码中的%替换所有.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'

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

答案 6 :(得分:33)

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

就我所知,%无法做到这一点。

答案 7 :(得分:29)

正如我今天发现的那样,通过%格式化字符串的旧方法不支持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()方法。

答案 8 :(得分:25)

在我的测试中,

%的效果优于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提供示例代码。

答案 9 :(得分:14)

作为旁注,您不必为了使用新的格式与日志记录而受到性能影响。您可以将任何对象传递给实现logging.debug魔术方法的logging.info__str__等。当日志记录模块确定它必须发出消息对象(无论它是什么)时,它会在执行此操作之前调用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

答案 10 :(得分:10)

如果您的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)

答案 11 :(得分:9)

%可能有用的一种情况是在格式化正则表达式时。例如,

'{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}}'。当你有两个正则表达式时,这可能很有用,其中一个正则单独使用而没有格式,但两者的串联都是格式化的。

答案 12 :(得分:5)

我想补充一点,因为版本3.6,我们可以使用类似下面的字符串

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

哪个给

  

我的名字是约翰史密斯

所有内容都转换为字符串

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

结果:

  

mylist = [&#39; foo&#39;,&#39; bar&#39;]

您可以传递功能,就像其他格式方法一样

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

举例来说

  

你好,这是日期:16/04/2018

答案 13 :(得分:2)

对于python版本&gt; = 3.6(参见PEP 498

s1='albha'
s2='beta'

f'{s1}{s2:>10}'

#output
'albha      beta'

答案 14 :(得分:1)

但是有一件事是,如果嵌套了花括号,则不能使用格式,但是%可以使用。

示例:

>>> '{{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}'
>>> 

答案 15 :(得分:1)

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

答案 16 :(得分:0)

严格地看到我们离原始主题真的很远,但是为什么不呢?

使用gettext模块提供例如本地化的GUI,新旧样式的字符串是唯一的方法; f弦不能在那里使用。恕我直言,新样式是这种情况下的最佳选择。 here上有一个SO问题。