Нужно ли импортировать логер в каждом декораторе или объявлять один раз выше
Что проверяют
Понимаете ли вы, как устроены импорт и реестр логеров, или повторяете миф про дорогой повторный импорт. Попутно смотрят, умеете ли вы называть логеры так, чтобы по записи было понятно, откуда она пришла.
Как отвечать
Стоимости вопроса тут почти нет. Модуль исполняется один раз, дальше import это поиск в sys.modules, а logging.getLogger(name) идемпотентен: на одно имя всегда возвращается один и тот же объект из внутреннего реестра. Значит выбирать надо не по производительности, а по имени логера.
import functools
import logging
logger = logging.getLogger(__name__)
def logged(func):
log = logging.getLogger(func.__module__)
@functools.wraps(func)
def wrapper(*args, **kwargs):
log.debug("вызов %s", func.__qualname__)
return func(*args, **kwargs)
return wrapperТонкость: если декоратор общий и лежит в утилитах, его собственное имя модуля в записях бесполезно, по нему не найти источник. Поэтому логер берут по func.__module__, причём один раз в момент декорирования, а не на каждом вызове. Аргументы подставляйте через %s, а не f-строкой: при выключенном уровне строка тогда вообще не собирается.
Где спотыкаются
Прячут импорты внутрь обёртки ради скорости. Первое исполнение модуля стоит времени в любом случае, дальше это словарный поиск, так что экономии нет, зато ошибка импорта всплывает не при старте, а на первом вызове в проде.
Теряют functools.wraps. У декорированной функции пропадают имя, докстрока и сигнатура, ломаются интроспекция, документация и код, который ищет функции по имени.
Настраивают логирование прямо в библиотечном модуле: зовут basicConfig или вешают обработчик на свой логер. Конфигурация принадлежит приложению, библиотека только пишет записи, иначе формат и уровни внезапно меняются у всех, кто её импортировал.