字符串格式:%与. 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示例看起来更.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

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

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

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

从 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

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
'\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另一个优点(我在答案中没有看到):它可以采用对象属性。

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'

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

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

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

%比测试中的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 字符串后,我在 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

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