字符串格式:%与.format


1348

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)


2
对于初学者:这是一个很好的教程,教了两种风格。我个人%更经常使用较旧的样式,因为如果您不需要format()样式的改进功能,则%样式通常会更方便。
Lutz Prechelt '16



1
要回答你的第二个问题,因为3.2可以使用{}格式,如果您使用自定义格式(见docs.python.org/3/library/logging.html#logging.Formatter
yanjost

Answers:


953

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


113
怎么样"%(a)s, %(a)s" % {'a':'test'}
TED 2012年

128
请注意,您将浪费时间,log.debug("something: %s" % x)但不会浪费时间log.debug("something: %s", x) 。字符串格式将在该方法中处理,并且如果不进行记录,则不会降低性能。与往常一样,Python会满足您的需求=)
darkfeline 2012年

63
泰德:与一样,这是一个看起来更糟的骇客'{0}, {0}'.format('test')
飞羊

19
重点是:一个反复出现的论点,即新语法允许对项目进行重新排序是有争议的:您可以对旧语法执行相同的操作。大多数人不知道这实际上已经在Ansi C99 Std中定义了!查阅的最新副本,man sprintf并了解占位符$内部的符号%
cfi

29
@cfi:如果您要printf("%2$d", 1, 3)打印出“ 3”,这是在POSIX中指定的,而不是C99。您参考的手册页上写道:“ C99标准不包括使用'$'的样式……”。
Thanatos

307

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

17
您可以像使用格式map一样轻松地使用旧样式格式。map('some_format_string_%s'.__mod__, some_iterable)
2012年

3
@cfi:请通过重写上面C99中的示例来证明您是对的
MarcH 2014年

9
@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手册页并没有列出,但允许库来实现的超集。我原来的论点仍然有效,取而代之的CPosix
cfi

8
我在这里的第一个评论不适用于此答案。我对此措辞感到遗憾。在Python中,我们不能使用取模运算符%对占位符进行重新排序。为了此处的评论一致,我仍然不想删除该第一条评论。我为在这里发泄怒气而道歉。反对经常做出的声明,即旧语法本身不允许这样做。除了创建全新的语法外,我们还可以引入std Posix扩展。我们可以同时拥有。
cfi 2014年

17
“模”是指除法运算后求余数的运算符。在这种情况下,百分号不是取模运算符。
章鱼

148

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

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

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


10
这是我现在才学到的一些有用的信息。可惜没有它自己的问题,因为它似乎与主要问题分开。可惜OP没有将他的问题分为两个独立的问题。
snth 2012年

12
您可以这样使用dict格式: log.debug("some debug info: %(this)s and %(that)s", dict(this='Tom', that='Jerry')) 但是,您不能在.format()这里使用新样式的语法,即使在Python 3.3中也不能使用,这很可惜。
Cito 2012年


26
这样做的主要好处不是性能(与您对日志输出进行的操作(例如在终端中显示,保存到磁盘)相比,字符串插值会更快),这是因为如果您有日志聚合器,可以告诉您“您收到此错误消息的12个实例”,即使它们都具有不同的“ some_info”值。如果在将字符串传递给log.debug之前完成了字符串格式化,那么这是不可能的。聚合器只能说“您有12条不同的日志消息”
Jonathan Hartley

7
如果你关心性能,使用文字字典{}语法,而不是一个字典()类的实例:doughellmann.com/2012/11/...
trojjer

119

从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


1
这不能回答问题。另一个提到F弦的答案至少谈到了性能:stackoverflow.com/a/51167833/7851470
Georgy

60

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


14
不正确:“可以通过保留现有机制来维持向后兼容性。” 当然,.format不会替换 %字符串格式。
Tobias

12
不,BrainStorms的假设是正确的:“旨在替代现有的'%'”。Tobias引用表示这两种系统将共存一段时间。RTFPEP
phobie,2015年

54

但是请小心,刚才我在尝试%.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'

或者参数字符串可以转换为字符串(所谓的“字节数组”)


12
除非format确实需要新方法的其他功能,否则根本没有理由更改工作代码……
Tobias 2013年

Tobias完全同意您的意见,但是在升级到新版本的Python时有时需
要这样做

2
例如?AFAIK,从未需要它;我不认为%字符串插值会消失。
Tobias

4
我认为.format()函数比%更安全。我经常看到初学者的错误,这样"p1=%s p2=%d" % "abc", 2"p1=%s p2=%s" % (tuple_p1_p2,)。您可能会认为这是编码器的错误,但我认为这只是怪异的错误语法,对quicky-scriptie而言不错,但对生产代码不利。
wobmene 2014年

3
但是我不喜欢.format()的语法,我会喜欢更好的old %s%02d例如"p1=%s p2=%02d".format("abc", 2)。我责怪那些发明并批准大括号格式的人,这些格式需要您逃避它们,{{}}并且看起来丑陋。
wobmene 2014年

35

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

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


4
与等效的相比,这看起来比必要的更难以理解'x is {0}, y is {1}'.format(a.x, a.y)。仅应在a.x操作成本很高时使用。
dtheodor

13
@dtheodor有了一个调整使用关键字参数,而不是位置参数... 'x is {a.x}, y is {a.y}'.format(a=a)。比这两个示例更具可读性。
CivFan 2015年

1
@CivFan或者,如果您有多个物体,'x is {a.x}, y is {a.y}'.format(**vars())
杰克

1
另请注意以下相同方式:'{foo[bar]}'.format(foo={'bar': 'baz'})
Antoine Pinsard '16

3
这对于面向客户的应用程序非常有用,其中您的应用程序提供了一组标准的格式设置选项以及用户提供的格式字符串。我经常用这个。例如,配置文件将具有一些“ 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 '16

35

%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


42
而是str.format提供更多功能(尤其是类型专用的格式,例如'{0:%Y-%m-%d}'.format(datetime.datetime.utcnow()))。性能并不是所有工作的绝对要求。使用正确的工具完成工作。
minhee 2011年

36
“过早的优化是万恶之源”,或者唐纳德·克努斯(Donald Knuth)曾经说过……
Yatharth Agarwal 2012年

22
坚持使用众所周知的格式设置方案(只要它适合大多数情况下的要求),并且速度快两倍,这并不是“过早的优化”,而是合理的。顺便说一句,%运营商允许重用printf知识;字典插值是该原理的非常简单的扩展。
Tobias

5
在一种情况下,我实际上遇到了相反的情况。新型格式化速度更快。可以提供您使用的测试代码吗?
大卫·桑德斯

8
似乎是严重浪费的帖子,没有任何示例或推理,只是声明。
kevr

31

正如我今天发现的那样,通过格式化字符串的旧方法%不支持Decimalpython的用于十进制定点和浮点算术的模块。

示例(使用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.00000000000000000000000312312239239000000000000000000

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


1
那可能是因为新样式格式str(d)在扩展参数之前先调用,而旧样式格式可能float(d)先调用。
大卫·桑德斯

3
您会这​​样想,但不会str(d)回来"3.12375239e-24""0.00000000000000000000000312375239000000000000000000"
杰克

18

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

15

附带说明,您不必为了提高性能而在日志记录中使用新样式格式。您可以将任何实现了magic方法的对象传递给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


2
我希望我能对此再投票。它允许在format不影响性能的情况下进行日志记录-通过__str__logging原本设计的方式进行覆盖-将函数调用缩短为单个字母(N),感觉与某些定义字符串的标准方法非常相似-并且允许延迟函数调用。谢谢!+1
CivFan 2015年

2
结果与使用logging.Formatter(style='{')参数有何不同?
大卫

10

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

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


3
或只是使用'{type_names} [a-z]{{2}}'.format(type_names='triangle|square')。这就像.format()在使用已经包含百分比字符的字符串时可以帮助您。当然。那你就得逃脱。
Alfe

1
@Alfe您是正确的,这就是为什么答案以开始的原因。"One situation where % may help is when you are formatting regex expressions."具体来说,假设您a=r"[a-z]{2}"是一个正则表达式块,您将在两个不同的最终表达式(例如c1 = b + ac2 = a)中使用该正则表达式。假设c1需要format编辑(例如,b需要对运行时进行格式化),但是c2不需要。然后,你需要a=r"[a-z]{2}"c2a=r"[a-z]{{2}}"c1.format(...)
豪尔赫·雷涛

7

我要补充一点,从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


4

对于python版本> = 3.6(请参阅PEP 498

s1='albha'
s2='beta'

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

#output
'albha      beta'

2

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

3
您应该多次运行每个示例,一次运行可能会产生误导,例如,操作系统通常可能很忙,因此延迟了代码的执行。请参阅docs:docs.python.org/3/library/timeit.html。(很好的头像,Guybrush!)
jake77 '19

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

2
您可以执行此操作,但是我同意这是很棒的'{{{0},{1}}}'。format(1、2)
Sylvan LE DEUNFF '18

嵌套关键字花括号有效且美观。
CivFan
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.