惯用Python日志:格式字符串+;参数列表与内联字符串格式-首选哪种格式?

惯用Python日志:格式字符串+;参数列表与内联字符串格式-首选哪种格式?,python,logging,coding-style,idioms,Python,Logging,Coding Style,Idioms,使用格式字符串+参数列表调用日志函数与使用格式内联调用日志函数相比是否有利 我见过(并编写了)使用内联字符串格式的日志代码: logging.warn("%s %s %s" % (arg1, arg2, arg3)) 但我认为使用以下命令(从性能角度看,也更惯用): 因为第二个表单在调用日志函数之前避免了字符串格式化操作。如果当前日志级别将过滤掉日志消息,则无需格式化,从而减少计算时间和内存分配 我是在正确的轨道上,还是错过了什么 如果这有帮助,下面是两个格式选项的快速计时测试: In [61

使用格式字符串+参数列表调用日志函数与使用格式内联调用日志函数相比是否有利

我见过(并编写了)使用内联字符串格式的日志代码:

logging.warn("%s %s %s" % (arg1, arg2, arg3))
但我认为使用以下命令(从性能角度看,也更惯用):

因为第二个表单在调用日志函数之前避免了字符串格式化操作。如果当前日志级别将过滤掉日志消息,则无需格式化,从而减少计算时间和内存分配


我是在正确的轨道上,还是错过了什么

如果这有帮助,下面是两个格式选项的快速计时测试:

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

似乎给了第二种方法以优势。

IMHO,对于很可能显示的消息,例如给
error
warn
的消息,没有多大区别

对于不太可能显示的消息,我肯定会选择第二个版本,主要是出于性能原因。我经常将大型对象作为参数赋给
info
,这实现了一种昂贵的
\uuuu str\uuu
方法。显然,将此预格式化信息发送到
info
会浪费性能

更新

我刚刚检查了
日志记录
模块的源代码,实际上,格式化是在检查日志级别之后完成的。例如:

class Logger(Filterer):
    # snip
    def debug(self, msg, *args, **kwargs):
        # snip
        if self.isenabledfor(debug):
            self._log(debug, msg, args, **kwargs)
可以观察到,在调用
log
和检查日志级别之间,
msg
args
未被触及

更新2

在Levon的启发下,让我为具有昂贵的
\uuu str\uu
方法的对象添加一些测试:

$ 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

实际上,这可以提供相当高的性能提升。

如果当前日志级别过滤日志消息(如我所料),避免内联字符串格式确实可以节省一些时间,但不会太多:

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

In [12]: %timeit -n 1000000 logger.warn('%s %s %s', 'a', 'b', 'c')
1000000 loops, best of 3: 946 ns per loop

因此,总体性能差异取决于格式化字符串所需的时间(这可能很大程度上取决于调用传递给日志函数的参数的成本)

我认为您的思路是对的。您是否有关于字符串格式化在不同时间发生的时间的参考资料?我希望这两种情况都发生在调用
warn
之前。对你所说的没有异议,只是想了解更多。在第一种形式中,我们在调用
日志记录之前进行字符串格式化(和元组创建)。warn
-在第二种情况下,我们只是将参数列表传递给
日志记录。warn
(这仅仅是元组创建?)-因此,我们在调用时避免了字符串格式化操作。正如我在问题中提到的,我假设如果当前日志记录级别将过滤掉日志消息,则不会进行任何格式化,这同样避免了字符串格式化操作。我的猜测是,这将节省处理时间和内存分配。@Inactivist啊。。好的,谢谢你的补充信息。我想你可以从我提供给你的信息中得出你自己的结论。我想另一种方法可能是对实际调用进行计时,并查看它们之间的比较情况?使用
log
函数对
%
的后续调用可能重复,以及后续调用的情况如何?因此,从您的回答中我可以学到的最重要的一课是:timeit是我的朋友!:D@Inactivist我发现它很有用。。通常,为了验证(或反驳)我关于什么是快速的,什么不是的假设:)有没有类似的简单方法来确定这两种变体之间的内存消耗差异?@Inactivist我也很想知道这一点,不幸的是没有找到一种。如果有,我很想知道。另一个可能产生影响的时间是日志记录是否处于性能关键循环中。这是我根据观察得出的预期。谢谢你去潜水!Python3.6f-string格式显示了相同的时间惩罚:
Python-m timeit-n 1000000-s“import logging”-s“logger=logging.getLogger('foo')”-s“logger.setLevel(logging.ERROR)”-s“x=list(range(0100))““logger.warn(f'{x}')”
每个循环产生10 usec,与经典样式的字符串格式相同。将格式化参数直接传递给log语句将产生2.12 usec。
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

In [12]: %timeit -n 1000000 logger.warn('%s %s %s', 'a', 'b', 'c')
1000000 loops, best of 3: 946 ns per loop