题 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中发生字符串格式化?例如,如果我的日志记录级别设置为HIGH,我仍然会执行以下操作 % 操作?如果是这样,有没有办法避免这种情况?

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

1178
2018-02-22 18:46


起源


如同 stackoverflow.com/questions/3691975/... - carl
@ S.Lott,重点是存在非零工作,如果日志语句没有打印我想要对字符串格式化做零工作。 - NorthIsUp
初学者:这是一个 非常好的教程 教两种风格。我个人使用较旧的 % 风格更经常,因为如果你不需要改进的功能了 format() 风格, % 风格往往更方便。 - Lutz Prechelt
供参考:Python 3文档 新 format() 格式化风格 和 旧的 %基于格式化的风格。 - Lutz Prechelt
也可以看看: Pythons有许多字符串格式化方法 - gerrit


答案:


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


852
2018-02-22 18:49



关于什么 "%(a)s, %(a)s" % {'a':'test'} - ted
请注意,您将浪费时间 log.debug("something: %s" % x) 但不是 log.debug("something: %s", x)  字符串格式化将在方法中处理,如果不记录,则不会获得性能影响。一如既往,Python预测您的需求=) - darkfeline
特德:这是一个看起来更糟糕的黑客做同样的事情 '{0}, {0}'.format('test')。 - flying sheep
重点是:新语法允许重新排序项目的一个反复出现的论点是一个有争议的问题:您可以使用旧语法执行相同操作。大多数人都不知道这实际上已经在Ansi C99标准中定义了!查看最近的副本 man sprintf 并了解 $ 里面的符号 % 占位符 - cfi
@cfi:如果你的意思是, printf("%2$d", 1, 3) 打印出“3”,这是在POSIX中指定的,而不是C99。您引用了注释的手册页,“C99标准不包括使用'$'的样式......”。 - Thanatos


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

281
2018-06-13 20:20



您可以使用旧样式格式 map 就像格式一样容易。 map('some_format_string_%s'.__mod__, some_iterable) - agf
@cfi:请在C99中重写上面的例子来证明你是对的 - MarcH
@游行: printf("%2$s %1$s\n", "One", "Two"); 用。编译 gcc -std=c99 test.c -o test,输出是 Two One。但我更正了: 它实际上是一个POSIX扩展 而不是C.我在C / C ++标准中再也找不到它,我以为我已经看过了。该代码甚至可以使用'c90'std标志。 sprintf 手册页。 这个 不会列出它,但允许libs实现超集。我的原始论点仍然有效,取而代之 C 同 Posix - cfi
我在这里的第一条评论并不适用于这个答案。我为这句话感到后悔。在Python中,我们不能使用模运算符 % 重新排序占位符。为了评论的一致性,我仍然不想删除第一条评论。我为在这里发泄我的愤怒而道歉。它针对的是经常声明的旧语法本身不允许这样做。我们可以引入std Posix扩展,而不是创建一个全新的语法。我们两个都可以。 - cfi
'modulo'是指在除法后评估余数的运算符。在这种情况下,百分号不是模运算符。 - Octopus


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

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

除非记录器实际记录某些东西,否则它会避免进行格式化。


125
2018-02-22 19:21



这是我刚刚学到的一些有用的信息。很遗憾它没有自己的问题,因为它似乎与主要问题分开。可惜OP没有在两个单独的问题中分开他的问题。 - snth
你可以像这样使用dict格式: log.debug("some debug info: %(this)s and %(that)s", dict(this='Tom', that='Jerry'))  但是,您无法使用新样式 .format() 语法在这里,甚至在Python 3.3中都没有,这是一种耻辱。 - Cito
@Cito:看到这个: plumberjack.blogspot.co.uk/2010/10/... - Vinay Sajip
这样做的主要好处不在于性能(与日志记录输出的任何操作相比,字符串插值都很快,例如在终端中显示,保存到磁盘)如果你有一个日志记录聚合器,它就是可以告诉你“你有12个这个错误消息的实例”,即使它们都有不同的'some_info'值。如果在将字符串传递给log.debug之前完成字符串格式化,那么这是不可能的。聚合器只能说“你有12条不同的日志消息” - Jonathan Hartley
如果您关注性能,请使用文字dict {}语法而不是dict()类实例化: doughellmann.com/2012/11/... - trojjer


从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


88
2018-04-15 11:12





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


54
2017-08-01 03:01



不真实:“通过保留现有机制,可以维持向后兼容性。”;当然, .format 惯于 更换  % 字符串格式。 - Tobias
不,BrainStorms假设是真的:“打算替代现有的'%'”。 Tobias引用意味着两个系统将共存一段时间。 RTFPEP - phobie


但是请小心,刚才我在尝试更换所有问题时发现了一个问题 % 同 .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'

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


51
2017-09-03 18:15



除非新功能的附加功能,否则没有理由更改工作代码 format 方法真的需要...... - Tobias
绝对赞同你,Tobias,但有时在升级到更新版本的Python时需要它 - rslnx
例如? AFAIK,它有 决不 需要;我认为不太可能 % 字符串插值将永远消失。 - Tobias
我认为.format()函数对于字符串比%更安全。我经常看到这样的初学者错误 "p1=%s p2=%d" % "abc", 2 要么 "p1=%s p2=%s" % (tuple_p1_p2,)。您可能认为这是编码器的错,但我认为这只是一种奇怪的错误语法,对于快速脚本看起来很不错但对生产代码却不好。 - rslnx
但我不喜欢.format()的语法,我会因为年老而感到高兴 %s, %02d 喜欢 "p1=%s p2=%02d".format("abc", 2)。我责怪那些发明并批准花括号格式的人,需要你像他们一样逃避它们 {{}} 看起来丑陋的imho。 - rslnx


又一个优点 .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'

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


33
2017-12-04 18:33



与同等物相比,这看起来比必要的更难以理解 'x is {0}, y is {1}'.format(a.x, a.y)。应该只在使用的时候使用 a.x 操作非常昂贵。 - dtheodor
@dtheodor通过调整来使用关键字参数而不是位置参数... 'x is {a.x}, y is {a.y}'.format(a=a)。比两个示例更具可读性。 - CivFan
@CivFan或者,如果你有多个对象, 'x is {a.x}, y is {a.y}'.format(**vars()) - Jack
还要以同样的方式注意这个: '{foo[bar]}'.format(foo={'bar': 'baz'})。 - Antoine Pinsard
这对于面向客户的应用程序非常有用,在这些应用程序中,您的应用程序使用用户提供的格式字符串提供一组标准格式选项。我经常用这个。例如,配置文件将具有一些“messagestring”属性,用户可以提供该属性 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} 或任何他们想要的。这比尝试使用旧格式化程序提供相同的功能要简单得多。它使用户提供的格式字符串更强大。 - Taywee


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


27
2018-05-13 17:10



这可能是因为新式格式化调用 str(d) 在扩展参数之前,而旧式格式化可能会调用 float(d) 第一。 - David Sanders
你是这么认为的,但是 str(d) 回报 "3.12375239e-24"不是 "0.00000000000000000000000312375239000000000000000000" - Jack


% 提供更好的性能 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提供的示例代码。


20
2018-06-13 18:43



代替, str.format 提供更多功能(特别是类型专用格式,例如 '{0:%Y-%m-%d}'.format(datetime.datetime.utcnow()))。绩效不是所有工作的绝对要求。使用正确的工具完成工作。 - minhee
“过早优化是万恶之源” 唐纳德克努特曾经说过...... - Yatharth Agarwal
坚持使用众所周知的格式化方案(只要它适合需求,它在绝大多数情况下都是如此),并且速度提高一倍,不是“过早优化”,而是合理的。 BTW, %运算符允许重用 printf 知识;字典插值是一个非常简单的原理扩展。 - Tobias
在一种情况下,我实际上经历了相反的情况。新式格式化更快。你能提供你用过的测试代码吗? - David Sanders
看起来像一个严重浪费的帖子,没有任何例子或推理,只是声称。 - kevr


作为旁注,您不必为了使用新的样式格式与日志记录而受到性能影响。您可以传递任何对象 logging.debuglogging.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


14
2017-08-21 18:00



我希望我能更多地投票。它允许使用 format 没有性能损失 - 通过覆盖来实现 __str__ 正如 logging 旨在 - 将函数调用缩短为单个字母(N)感觉非常类似于定义字符串的一些标准方法 - AND允许惰性函数调用。谢谢! +1 - CivFan
这与使用的结果有什么不同 logging.Formatter(style='{') 参数? - meowsqueak