Python 字符串格式:%vs..format vs.f-String literal

Python 字符串格式:%vs..format vs.f-String literal,python,performance,logging,string-formatting,f-string,Python,Performance,Logging,String Formatting,F String,Python2.6使用与现有的%运算符稍有不同的语法引入了该方法。在什么情况下,哪一个更好 Python3.6现在通过语法f“my string”引入了另一种字符串文本格式(也称为“f”字符串)。这个格式选项比其他的好吗 下面使用的每种方法都有相同的结果,那么有什么区别呢 #!/usr/bin/python sub1 = "python string!" sub2 = "an arg" sub_a = "i am a %s"

Python2.6使用与现有的
%
运算符稍有不同的语法引入了该方法。在什么情况下,哪一个更好

Python3.6现在通过语法
f“my string”
引入了另一种字符串文本格式(也称为“f”字符串)。这个格式选项比其他的好吗

  • 下面使用的每种方法都有相同的结果,那么有什么区别呢

     #!/usr/bin/python
     sub1 = "python string!"
     sub2 = "an arg"
    
     sub_a = "i am a %s" % sub1
     sub_b = "i am a {0}".format(sub1)
     sub_c = f"i am a {sub1}"
    
     arg_a = "with %(kwarg)s!" % {'kwarg':sub2}
     arg_b = "with {kwarg}!".format(kwarg=sub2)
     arg_c = f"with {sub2}!"
    
     print(sub_a)    # "i am a python string!"
     print(sub_b)    # "i am a python string!"
     print(sub_c)    # "i am a python string!"
    
     print(arg_a)    # "with an arg!"
     print(arg_b)    # "with an arg!"
     print(arg_c)    # "with an arg!"
    
  • 此外,Python中何时出现字符串格式?例如,如果我的日志记录级别设置为高,我是否仍会因为执行以下
    %
    操作而受到攻击?如果是这样,有没有办法避免这种情况

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

  • 要回答你的第一个问题
    .format
    在许多方面似乎更复杂。
    %
    的一个恼人之处也是它如何接受变量或元组。你可能会认为以下方法总是有效的:

    "hi there %s" % name
    
    然而,如果
    name
    恰好是
    (1,2,3)
    ,它将抛出
    类型错误。为了保证它总是打印出来,你需要

    "hi there %s" % (name,)   # supply the single argument as a single-item tuple
    
    这太难看了
    .format
    没有这些问题。同样在您给出的第二个示例中,
    .format
    示例看起来更简洁

    你为什么不使用它

    • 不知道(我在读这篇文章之前)
    • 必须与Python 2.5兼容


    要回答第二个问题,字符串格式设置与任何其他操作同时发生-在计算字符串格式设置表达式时。Python不是一种懒惰的语言,它在调用函数之前对表达式求值,因此在您的
    log.debug
    示例中,表达式
    “某些调试信息:%s”%some_info
    将首先求值为,例如
    “某些调试信息:roflopter处于活动状态”
    ,然后该字符串将传递给
    log.debug()

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

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

    这避免了格式化,除非记录器实际记录了一些内容。

    %
    比我测试的
    格式提供了更好的性能

    测试代码:

    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
    
    > format: 0.86600608
    > %: 0.630180146
    
    format: 0.8331376779999999
    %: 0.6314778750000001
    f-string: 0.766649943
    
    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中,
    %
    格式
    快得多

    感谢@Chris Cogdon提供的示例代码

    编辑1:

    2019年7月在Python 3.7.2中再次测试

    结果:

    > format: 0.470329046249
    > %: 0.357107877731
    
    > format: 0.86600608
    > %: 0.630180146
    
    format: 0.8331376779999999
    %: 0.6314778750000001
    f-string: 0.766649943
    
    差别不大。我想Python正在逐渐改进

    编辑2:

    在有人在评论中提到python 3的f-string之后,我在python 3.7.2下对以下代码进行了测试:

    import timeit
    print('format:',timeit.timeit('{}{}{}'。format(1,1.23,'hello'))
    打印(“%:”,timeit.timeit(“%s%s%s%”(1,1.23,'hello'))
    print('f-string:',timeit.timeit(“f'{1}{1.23}{\'hello\'}'))
    
    结果:

    > format: 0.470329046249
    > %: 0.357107877731
    
    > format: 0.86600608
    > %: 0.630180146
    
    format: 0.8331376779999999
    %: 0.6314778750000001
    f-string: 0.766649943
    

    似乎f-string仍然比
    %
    慢,但比
    格式

    好,这是模运算符(%)无法做到的,好吧:

    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 3中新的高级字符串格式替换
    %
    运算符,这将是默认值。

    但是请小心,刚才我在尝试用现有代码中的
    .format
    替换所有
    %
    时发现了一个问题:
    '{}.format(unicode\u字符串)
    将尝试编码unicode\u字符串,可能会失败。

    只需查看以下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中称为“字符串”):

    将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)
    
    只有当原始字符串是Unicode时,它才能与Unicode参数一起工作

    ; '{}'.format(u'i')
    'i'
    

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

    就像我今天发现的那样,通过
    %
    格式化字符串的旧方法不支持
    Decimal
    ,Python的十进制定点和浮点算术模块,即开箱即用

    示例(使用Python 3.3.5):

    输出:

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


    当然可能会有一些工作,但是你仍然可以考虑立即使用<代码>格式化()/<代码>方法。 作为旁注,在日志记录中使用新样式的格式不必受到性能的影响。您可以将任何对象传递给

    logging.debug
    logging.info
    等,这些对象实现了
    \uuu str\uuu
    魔术方法。当日志模块决定它必须发出消息对象(不管它是什么)时,它会在发出之前调用
    str(message\u 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文档()中都有描述。但是,它也可以与Python2.6配合使用()


    使用此技术的一个优点是,它允许惰性值,例如上面的函数
    pricine_func
    ,而不是格式化样式不可知。这为Python文档中给出的建议提供了一个更优雅的替代方案:.

    另一个优点是
    .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'
    
    据我所知,这在
    %
    情况下是不可能的。

    有一种情况
    foo = "john"
    bar = "smith"
    print(f"My name is {foo} {bar}")
    
    mylist = ["foo", "bar"]
    print(f"mylist = {mylist}")
    
    print(f'Hello, here is the date : {time.strftime("%d/%m/%Y")}')
    
    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)
    
    >>> '{{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}'
    >>> 
    
    #!/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
    -----