相关文章推荐
帅呆的炒粉  ·  pytorch ...·  2 月前    · 
性感的斑马  ·  【腾讯云 Cloud Studio ...·  1 年前    · 

简洁的Python日志:格式化字符串+args列表vs.内联字符串格式化--哪一个是首选?

20 人关注

用format string + args list调用日志函数与内联格式化相比,是否有优势?

我见过(也写过)使用内联字符串格式化的日志代码。

logging.warn("%s %s %s" % (arg1, arg2, arg3))

而我却认为它是better(从性能上讲,更多的是成语)来使用。

logging.warn("%s %s %s", arg1, arg2, arg3)

因为第二种形式避免了在调用日志功能之前的字符串格式化操作。 如果当前的日志级别会过滤掉日志信息,就不需要进行格式化,减少了计算时间和内存分配。

我在这里是否走对了路,或者我错过了什么?

5 个评论
我认为你的方向是正确的。
你有关于字符串格式化在不同时间发生的时间的参考资料吗?我希望两者都是在调用 warn 之前发生的。我不是质疑你所说的,只是想知道更多。
好吧,在第一个表格中,我们正在做字符串格式化( 元组创建) prior logging.warn 的调用。- 在第二种情况下,我们只是向 logging.warn 传递了一个args列表(这只是创建元组? 正如我在问题中提到的,我假设如果当前的日志级别会过滤掉日志信息,就不会进行格式化,同样避免了字符串格式化的操作。 我的猜想是这样的 should save processing time 和 memory allocations.
@Inactivist 啊......好的,谢谢你的补充信息。我想你可以从我提供的时间和你已有的信息中得出你自己的结论。我想另一种方法可能是对实际的调用进行计时,看看它们如何比较?
python
logging
coding-style
idioms
Inactivist
Inactivist
发布于 2012-08-14
3 个回答
user1202136
user1202136
发布于 2012-08-15
已采纳
0 人赞同

IMHO,对于那些非常有可能被显示的信息,如那些给 error warn 的信息,并没有太大的区别。

对于那些不太可能显示的信息,我肯定会选择第二个版本,主要是出于性能的考虑。我经常给大型对象作为 info 的参数,它实现了一个昂贵的 __str__ 方法。显然,把这个预先格式化的东西发送到 info 将是一种性能浪费。

我刚刚检查了 logging 模块的源代码,的确,格式化已经完成。 之后 检查日志级别。比如说。

class Logger(Filterer):
    # snip
    def debug(self, msg, *args, **kwargs):
        # snip
        if self.isenabledfor(debug):
            self._log(debug, msg, args, **kwargs)

我们可以观察到,在调用msgargs和检查日志级别之间,log没有被触动。

在Levon的监视下,让我为那些有昂贵的__str__方法的对象添加一些测试。

$ python -m timeit -n 1000000 -s "import logging" -s "logger = logging.getLogger('foo')" -s "logger.setLevel(logging.ERROR)" "logger.warn('%s', range(0,100))"
1000000 loops, best of 3: 1.52 usec per loop
$ python -m timeit -n 1000000 -s "import logging" -s "logger = logging.getLogger('foo')" -s "logger.setLevel(logging.ERROR)" "logger.warn('%s' % range(0,100))"
1000000 loops, best of 3: 10.4 usec per loop

在实践中,这可以带来相当高的性能提升。

另一个可能产生影响的情况是,如果记录是在一个性能关键的循环中。
根据观察,这正是我所期望的。 谢谢你去找源头!
Python 3.6 f-string格式化显示了同样的时间惩罚。替换代码0】在每个循环中产生10usec,与经典风格的字符串格式化相同。 将格式化参数直接传递给log语句可以得到2.12usec。
Levon
Levon
发布于 2012-08-15
0 人赞同

如果这有帮助的话,这里有一个快速的时间测试,只针对两个格式化选项。

In [61]: arg1='hello'
In [62]: arg2='this'
In [63]: arg3='is a test'
In [70]: timeit -n 10000000 "%s %s %s" % (arg1, arg2, arg3)
10000000 loops, best of 3: 284 ns per loop
In [71]: timeit -n 10000000  "%s %s %s", arg1, arg2, arg3
10000000 loops, best of 3: 119 ns per loop

似乎第二种方法更有优势。

那么,在 log 函数中对 % 的后续调用又是什么情况?
所以,我从你的回答中可以学到的最重要的一课:时间是我的朋友! :D
@Inactivist 我发现它很有用......经常可以验证(或反驳)我对什么是快速和什么是不快速的假设 :)
是否有类似的 simple 如何确定两个变体之间的内存消耗差异?
@Inactivist 我也曾想过这个问题,不幸的是有 not found one. If there's one, I'd love to know though.
Inactivist
Inactivist
发布于 2012-08-15
0 人赞同

如果当前的日志级别对日志信息进行过滤,避免内联字符串格式化确实可以节省一些时间(正如我所预期的那样)--但不是很多。

In [1]: import logging
In [2]: logger = logging.getLogger('foo')
In [3]: logger.setLevel(logging.ERROR)
In [4]: %timeit -n 1000000 logger.warn('%s %s %s' % ('a', 'b', 'c'))
1000000 loops, best of 3: 1.09 us per loop