Как я могу удалить звонки Python, не комментируя их? - PullRequest
28 голосов
/ 07 февраля 2009

Сегодня я думал о проекте Python, который написал около года назад, где довольно широко использовал logging. Я помню, что мне приходилось комментировать множество вызовов журналирования в сценариях, подобных внутренним циклам (код 90%), из-за накладных расходов (hotshot указало, что это было одним из моих самых больших узких мест).

Интересно, есть ли какой-то канонический способ программно вырезать записи вызовов в приложениях Python без постоянного комментирования и раскомментирования. Я думаю, вы могли бы использовать проверку / перекомпиляцию или манипулирование байт-кодом, чтобы сделать что-то подобное, и предназначаться только для тех объектов кода, которые вызывают узкие места . Таким образом, вы можете добавить манипулятор в качестве шага после компиляции и использовать централизованный файл конфигурации, например:

[Leave ERROR and above]
my_module.SomeClass.method_with_lots_of_warn_calls

[Leave WARN and above]
my_module.SomeOtherClass.method_with_lots_of_info_calls

[Leave INFO and above]
my_module.SomeWeirdClass.method_with_lots_of_debug_calls

Конечно, вы хотели бы использовать его экономно и, вероятно, с гранулярностью для каждой функции - только для объектов кода, которые показали, что logging является узким местом. Кто-нибудь знает что-нибудь подобное?

Примечание: Есть несколько вещей, которые делают это более трудным для выполнения быстрым способом из-за динамической типизации и позднего связывания. Например, любые вызовы метода с именем debug могут быть заключены в if not isinstance(log, Logger). В любом случае, я предполагаю, что все мелкие детали могут быть преодолены либо джентльменским соглашением, либо некоторой проверкой во время выполнения. : -)

Ответы [ 10 ]

20 голосов
/ 08 февраля 2009

Как насчет использования logging.disable ?

Я также обнаружил, что должен был использовать logging.isEnabledFor , если создание сообщения регистрации требует больших затрат.

5 голосов
/ 16 марта 2011

Использование pypreprocessor

Который также можно найти в PYPI (индекс пакета Python) и получить с помощью pip.

Вот базовый пример использования:

from pypreprocessor import pypreprocessor

pypreprocessor.parse()

#define nologging

#ifdef nologging
...logging code you'd usually comment out manually...
#endif

По сути, препроцессор комментирует код так, как вы делали это вручную. Он просто делает это на лету условно в зависимости от того, что вы определяете.

Вы также можете удалить все директивы препроцессора и закомментированный код из кода, обработанного постобработкой, добавив «pypreprocessor.removeMeta = True» между импортом и операторы parse ().

Файл вывода байт-кода (.pyc) будет содержать оптимизированный вывод.

SideNote: pypreprocessor совместим с python2x и python3k.

Отказ от ответственности: я являюсь автором pypreprocessor.

5 голосов
/ 04 апреля 2009

Я также видел использование assert таким способом.

assert logging.warn('disable me with the -O option') is None

(я предполагаю, что предупреждение всегда возвращает ничего .. если нет, вы получите AssertionError

Но на самом деле это просто забавный способ сделать это:

if __debug__: logging.warn('disable me with the -O option')

Когда вы запускаете скрипт с этой строкой в ​​нем с параметром -O, строка будет удалена из оптимизированного кода .pyo. Если вместо этого у вас была собственная переменная, как в следующем примере, у вас будет условное условие, которое всегда выполняется (независимо от значения переменной), хотя условное условие должно выполняться быстрее, чем вызов функции:

my_debug = True
...
if my_debug: logging.warn('disable me by setting my_debug = False')

, так что, если я правильно понимаю debug , то это хороший способ избавиться от ненужных вызовов в журнале. Обратной стороной является то, что он также отключает все ваши утверждения, так что это проблема, если вам нужны утверждения.

2 голосов
/ 07 февраля 2009

Как несовершенный ярлык, как насчет макетирования logging в определенных модулях, используя что-то вроде MiniMock ?

Например, если my_module.py было:

import logging
class C(object):
    def __init__(self, *args, **kw):
        logging.info("Instantiating")

Вы бы заменили использование my_module на:

from minimock import Mock
import my_module
my_module.logging = Mock('logging')
c = my_module.C()

Вам нужно будет сделать это только один раз, до первоначального импорта модуля.

Получение поведения, специфичного для уровня, было бы достаточно простым путем насмешки над определенными методами или с помощью logging.getLogger, возвращающего фиктивный объект с одними бессильными методами, а другие делегировали бы действительному модулю logging.

На практике вы, вероятно, захотите заменить MiniMock на что-то более простое и быстрое; по крайней мере, то, что не печатает использование в stdout! Конечно, это не решает проблему модуля A, импортирующего logging из модуля B (и, следовательно, A также импортирующего гранулярность журнала B) ...

Это никогда не будет так быстро, как вообще не запускать операторы log, но должно быть намного быстрее, чем полностью входить в глубины модуля логирования, только чтобы обнаружить, что эта запись не должна регистрироваться.

1 голос
/ 09 июня 2014

В настоящее время я работаю над проектом, в котором используется обширное ведение журнала для проверки логики и времени выполнения API анализа данных с использованием библиотеки Pandas.

Я нашел эту строку с похожей проблемой - например, Каковы издержки на операторах logging.debug, даже если для уровня logging.basicConfig установлено значение level = logging.WARNING

Я прибег к написанию следующего сценария, чтобы закомментировать или раскомментировать ведение журнала отладки перед развертыванием:

import os
import fileinput

comment = True

# exclude files or directories matching string
fil_dir_exclude = ["__","_archive",".pyc"]

if comment :
    ## Variables to comment
    source_str = 'logging.debug'
    replace_str = '#logging.debug'
else :
    ## Variables to uncomment
    source_str = '#logging.debug'
    replace_str = 'logging.debug'

# walk through directories
for root, dirs, files in os.walk('root/directory') :
    # where files exist
    if files:
        # for each file
        for file_single in files :
            # build full file name
            file_name = os.path.join(root,file_single)
            # exclude files with matching string
            if not any(exclude_str in file_name for exclude_str in fil_dir_exclude) :
                # replace string in line
                for line in fileinput.input(file_name, inplace=True):
                    print "%s" % (line.replace(source_str, replace_str)),

Это рекурсия файла, которая исключает файлы на основе списка критериев и выполняет замену на месте на основе найденного здесь ответа: Поиск и замена строки в файле на Python

1 голос
/ 11 мая 2010

:-) Мы привыкли называть это препроцессором, и хотя у препроцессора C были некоторые из этих возможностей, «король горы» был препроцессором для мэйнфрейма IBM PL / I. Он обеспечивал обширную языковую поддержку в препроцессоре (полные назначения, условия, циклы и т. Д.), И было возможно писать «программы, которые писали программы», используя только PL / I PP.

Я написал много приложений с полноценной сложной программой и трассировкой данных (у нас не было приличного отладчика для внутреннего процесса в то время) для использования при разработке и тестировании, которые затем, когда компилировались с соответствующими " Флаг времени выполнения "просто удаляет весь код трассировки без какого-либо влияния на производительность.

Я думаю, что идея декоратора хорошая. Вы можете написать декоратор, чтобы обернуть функции, которые требуют регистрации. Затем для распространения во время выполнения декоратор превращается в «no-op», что исключает операторы отладки.

Джон Р

1 голос
/ 07 марта 2009

Это проблема и для моего проекта - регистрация в отчетах профилировщика заканчивается довольно последовательно.

Я уже использовал модуль _ast в развилке PyFlakes (http://github.com/kevinw/pyflakes) ... и определенно можно сделать то, что вы предлагаете в своем вопросе - проверять и вводить охранники перед вызовами входа в систему методы (с вашим подтвержденным предупреждением, что вам нужно будет выполнить некоторую проверку типов во время выполнения). См. http://pyside.blogspot.com/2008/03/ast-compilation-from-python.html для простого примера.

Редактировать: Я только что заметил MetaPython в моем фиде planetpython.org - пример использования - удаление операторов журнала во время импорта.

Возможно, лучшим решением было бы переопределить ведение журнала как модуль C, но я не был бы первым, кто воспользовался бы такой ... возможностью: p

1 голос
/ 07 февраля 2009

Я бы использовал какой-нибудь необычный декоратор журналирования или их кучу:

def doLogging(logTreshold):
    def logFunction(aFunc):
        def innerFunc(*args, **kwargs):
            if LOGLEVEL >= logTreshold:
                print ">>Called %s at %s"%(aFunc.__name__, time.strftime("%H:%M:%S"))
                print ">>Parameters: ", args, kwargs if kwargs else "" 
            try:
                return aFunc(*args, **kwargs)
            finally:
                print ">>%s took %s"%(aFunc.__name__, time.strftime("%H:%M:%S"))
        return innerFunc
    return logFunction

Все, что вам нужно, это объявить константу LOGLEVEL в каждом модуле (или просто глобально и просто импортировать ее во все модули), а затем вы можете использовать ее следующим образом:

@doLogging(2.5)
def myPreciousFunction(one, two, three=4):
    print "I'm doing some fancy computations :-)"
    return

И если LOGLEVEL не меньше 2,5, вы получите вывод, подобный этому:

>>Called myPreciousFunction at 18:49:13
>>Parameters:  (1, 2) 
I'm doing some fancy computations :-)
>>myPreciousFunction took 18:49:13

Как видите, для лучшей обработки kwargs требуется некоторая работа, поэтому значения по умолчанию будут напечатаны, если они присутствуют, но это другой вопрос.

Вы, вероятно, должны использовать какой-то модуль logger вместо необработанных print операторов, но я хотел сосредоточиться на идее декоратора и не делать код слишком длинным.

В любом случае - с таким декоратором вы получаете протоколирование на уровне функций, произвольное количество уровней журналирования, простоту применения к новой функции и отключение журналирования, вам нужно только установить LOGLEVEL. И вы можете определить различные выходные потоки / файлы для каждой функции, если хотите. Вы можете написать doLogging как:

 def doLogging(logThreshold, outStream=sys.stdout):
      .....
      print >>outStream, ">>Called %s at %s" etc.

И использовать файлы журналов, определенные для каждой функции.

1 голос
/ 07 февраля 2009

Вы можете попробовать что-то вроде этого:

# Create something that accepts anything
class Fake(object):
    def __getattr__(self, key):
        return self
    def __call__(self, *args, **kwargs):
        return True

# Replace the logging module
import sys
sys.modules["logging"] = Fake()

По существу, он заменяет (или изначально заполняет) пространство для модуля протоколирования на экземпляр Fake, который просто принимает все. Вы должны запустить приведенный выше код (только один раз!), Прежде чем модуль журналирования будет использоваться где-либо еще. Вот тест:

import logging

logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s %(levelname)-8s %(message)s',
                    datefmt='%a, %d %b %Y %H:%M:%S',
                    filename='/temp/myapp.log',
                    filemode='w')
logging.debug('A debug message')
logging.info('Some information')
logging.warning('A shot across the bows')

С учетом вышесказанного ничего не было зарегистрировано, как и следовало ожидать.

0 голосов
/ 08 февраля 2010

Мне нравится решение 'if __debug_', за исключением того, что размещение его перед каждым вызовом немного отвлекает и уродливо. У меня была такая же проблема, и я преодолел ее, написав скрипт, который автоматически анализирует ваши исходные файлы и заменяет операторы журналирования на операторы pass (и комментирует копии операторов журналирования). Это также может отменить это преобразование.

Я использую его при развертывании нового кода в производственной среде, когда есть много операторов ведения журналов, которые мне не нужны в рабочих настройках, и они влияют на производительность.

Вы можете найти скрипт здесь: http://dound.com/2010/02/python-logging-performance/

...