Идиоматическое ведение журнала Python: форматирование строки + список аргументов против форматирования строки строки - что предпочтительнее?
Полезно ли вызывать функции ведения журнала с помощью строки формата + список аргументов против форматирования inline?
Я видел (и написал) код регистрации, который использует форматирование строки inline:
logging.warn("%s %s %s" % (arg1, arg2, arg3))
и все же я предполагаю, что лучше (с точки зрения производительности и более идиоматического) использовать:
logging.warn("%s %s %s", arg1, arg2, arg3)
потому что вторая форма избегает операций форматирования строк до вызова функции ведения журнала. Если текущий уровень ведения журнала отфильтровывает сообщение журнала, форматирование не требуется, что сокращает время обработки и распределение памяти.
Я на правильном пути здесь, или я что-то пропустил?
Ответы
Ответ 1
IMHO, для сообщений, которые, скорее всего, будут отображаться, например, для error
или warn
, это не имеет большого значения.
Для сообщений, которые менее вероятно отображаются, я бы определенно пошел на вторую версию, главным образом по соображениям производительности. Я часто передаю большие объекты в качестве параметра info
, которые реализуют дорогостоящий метод __str__
. Понятно, что отправкой этого предварительно отформатированного в info
будет потеря производительности.
UPDATE
Я только что проверил исходный код модуля logging
, и, действительно, форматирование выполняется после проверки уровня журнала. Например:
class Logger(Filterer):
# snip
def debug(self, msg, *args, **kwargs):
# snip
if self.isenabledfor(debug):
self._log(debug, msg, args, **kwargs)
Можно заметить, что msg
и args
не связаны между вызовом log
и проверкой уровня журнала.
ОБНОВЛЕНИЕ 2
Выделив Левона, позвольте мне добавить несколько тестов для объектов с дорогостоящим методом __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
На практике это может привести к значительному повышению производительности.
Ответ 2
В случае, если это полезно, вот быстрый анализ времени для двух параметров форматирования:
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
кажется, дает второй подход к краю.
Ответ 3
Избегая форматирования строки в строке, сохраняется некоторое время, если текущий уровень ведения журнала фильтрует сообщение журнала (как я ожидал) - но не много:
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
Итак, поскольку user1202136 указал, общая разница в производительности зависит от того, сколько времени требуется для форматирования строки (что может быть значительным в зависимости от стоимости вызова __str__
по аргументам, переданным функции регистрации.)