performance - python字符串格式化: % vs. .format

  显示原文与译文双语对照的内容

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 中什么时候进行字符串格式化? 例如,如果我的日志级别设置为高我仍然会执行以下 % 操作了? 如果是这样的话,是否有办法避免?

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

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

模运算符( % ) 不能执行的操作,例如:


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

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


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

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

另外,PEP 3101 建议用 python 3中的新的高级字符串格式替换 % 运算符,其中将是默认的。

但是请小心,刚才我发现一个问题,当试图用 .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
'xd0xb9'
; u
u'u0439'

s 只是一个字符串( 调用名'字节数组'在Python3中) 和 u 是一个Unicode字符串( 调用名'字符串'在Python3中):


; '%s' % s
'xd0xb9'
; '%s' % u
u'u0439'

将一个Unicode对象作为参数传递给 % 运算符时,即使原始字符串不是Unicode字符串,它也会生成一个Unicode字符串:


; '{}'.format(s)
'xd0xb9'
; '{}'.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'xd0xb9'
; 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 = 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.000 00000000000000000000312375239000000000000000000

当然可能会有 work-arounds,但是你还是可以立即考虑使用 format() 方法。

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

对于 % 来说,这是不可能的。

使用 timeit 我得到 format1.17x 更快 ( 没有多少) 比使用 % 操作符。 这是我的测试:


>>>timeit("%d and %.1f" % (4,2.2) )
1000000 loops, best of 3: 1.1 us per loop

>>>timeit("{} and {}".format(4,2.2) )
1000000 loops, best of 3: 940 ns per loop

...