字符串格式化:百分号 vs .format vs f-string字面值

1453

有各种不同的字符串格式化方法:

  • Python <2.6: "Hello %s" % name
  • Python 2.6+: "Hello {}".format(name)   (使用str.format)
  • Python 3.6+: f"{name}"   (使用f-strings)

哪个更好,适用于哪些情况?


  1. 以下这些方法有相同的结果,那么它们之间有什么区别?

  2. name = "Alice"
    
    "Hello %s" % name
    "Hello {0}".format(name)
    f"Hello {name}"
    
    # Using named arguments:
    "Hello %(kwarg)s" % {'kwarg': name}
    "Hello {kwarg}".format(kwarg=name)
    f"Hello {name}"
    
  3. 字符串格式化是在什么时候运行的,如何避免运行时的性能损失?


如果您正在尝试关闭一个寻找字符串格式化方法的重复问题,请使用如何将变量值放入字符串中?


2
与 https://dev59.com/AHA65IYBdhLWcg3wrQp4 类似。 - carl
3
对于初学者:这是一个非常好的教程,教授了两种格式。我个人更倾向于使用旧的“%”格式,因为如果您不需要“format()”格式的改进功能,那么“%”格式通常更加方便。请点击这里查看教程:http://www.python-course.eu/python3_formatted_output.php - Lutz Prechelt
2
参考文献:Python 3文档中的新式“format()”格式化风格和旧式基于“%”的格式化风格。请查看以下链接:https://docs.python.org/3/library/string.html#format-string-syntax 和 https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting。 - Lutz Prechelt
1
回答你的第二个问题,从3.2版本开始,如果使用自定义格式化程序(参见https://docs.python.org/3/library/logging.html#logging.Formatter),则可以使用{}格式。 - yanjost
显示剩余2条评论
16个回答

16

顺便提一句,如果你想在日志记录中使用新风格的格式化,不需要损失性能。你可以将任何实现了__str__魔法方法的对象传递给logging.debuglogging.info等函数。当日志模块决定必须发出消息对象(无论它是什么)时,它会在这样做之前调用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
2
使用 logging.Formatter(style='{') 参数与此有何不同? - davidA

12

在格式化正则表达式时,%可能会有所帮助。例如,

'{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}}'。当你有两个正则表达式,其中一个单独使用而不进行格式化,但两者连接在一起会被格式化时,这种方法很有用。


4
或者只需使用 '{type_names} [a-z]{{2}}'.format(type_names='triangle|square')。这就像是在说当使用已经包含百分号字符的字符串时,可以使用 .format() 来帮助解决问题。当然,你需要对它们进行转义。 - Alfe
2
@Alfe 您是正确的,这就是答案以 "One situation where % may help is when you are formatting regex expressions." 开头的原因。具体而言,假设 a=r"[a-z]{2}" 是一个正则表达式块,将在两个不同的最终表达式中使用(例如 c1 = b + ac2 = a)。假设 c1 需要运行时进行 格式化 (例如需要格式化 b),但 c2 不需要。那么您需要 a=r"[a-z]{2}" 来为 c2 提供支持,并且需要 a=r"[a-z]{{2}}" 来为 c1.format(...) 提供支持。 - Jorge Leitao

8
自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")}')

举个例子

你好,这里是日期:2018年4月16日


6

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

4
你应该多次运行每个示例,单次运行可能会误导,例如操作系统可能普遍繁忙,因此你的代码执行会被延迟。请查看文档:https://docs.python.org/3/library/timeit.html。(很棒的头像,Guybrush!) - jake77

5

对于 Python 版本>= 3.6(参见PEP 498),以下是需要注意的:

s1='albha'
s2='beta'

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

#output
'albha      beta'

3

但有一件事是,即使您有嵌套的花括号,也无法使用format,但可以使用%

例子:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接