Как бы вы написали @debuggable декоратор на python?

При отладке я предпочитаю распечатывать все входы и выходы функции (я знаю, что мне нужна лучшая IDE, но, поверьте мне, это можно использовать для отчетов об ошибках). Итак, в идеале я хотел бы иметь:

@debuggable
def myfunc(argA,argB,argC):
    return argB+1

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

Лучшее, что я могу придумать, это:

DEBUG = True

def debuggable(func):
    if DEBUG:
        def decorated(*args):
            print "Entering ",func.func_name
            print "    args ",args
            ret = func(*args)
            print ret
            return ret
        return decorated
    else:
        return func

@debuggable
def myfunc(this,that):
    return this+that

И работает:

>>> myfunc(1,3)
Entering  myfunc
   args  (1, 3)
4

Как я могу улучшить это?

6 ответов

Решение

Используйте отладчик. Шутки в сторону. Украшение каждой функции, которую вы хотите отслеживать, - плохая идея.

В Python включен отладчик, поэтому вам не нужна хорошая IDE.

Если вы не хотите использовать отладчик, вы можете использовать функцию трассировки.

import sys

@sys.settrace
def trace_debug(frame, event, arg):
    if event == 'call':
        print ("calling %r on line %d, vars: %r" % 
                (frame.f_code.co_name, 
                 frame.f_lineno,
                 frame.f_locals))
        return trace_debug
    elif event == "return":
        print "returning", arg

def fun1(a, b):
    return a + b

print fun1(1, 2)

Это печатает:

calling 'fun1' on line 14, vars: {'a': 1, 'b': 2}
returning 3
3

Еще проще было бы использовать Winpdb:

Это независимый от платформы графический GPL-отладчик Python с поддержкой удаленной отладки по сети, несколькими потоками, модификацией пространства имен, встроенной отладкой, зашифрованной связью и работает в 20 раз быстрее, чем pdb.

Особенности:

  • Лицензия GPL. Winpdb является свободным программным обеспечением.
  • Совместим с CPython 2.3 или новее.
  • Совместим с wxPython 2.6 или новее.
  • Независимо от платформы и протестировано на Ubuntu Gutsy и Windows XP.
  • Пользовательские интерфейсы: rpdb2 основан на консоли, в то время как winpdb требует wxPython 2.6 или новее.

http://winpdb.org/images/screenshot_winpdb_small.jpg

Я думаю, что вы ищете не декоратор отладки, а декоратор журналирования.

Возможно, имеет смысл использовать модуль журналирования Python, чтобы вы могли иметь более точный контроль над самой регистрацией. Например, вы сможете выводить в файл для последующего анализа вывода.

Декоратор может выглядеть примерно так:


import logging

logger = logging.getLogger('TraceLog')
# TODO configure logger to write to file/stdout etc, it's level etc


def logthis(level):
    def _decorator(fn):
        def _decorated(*arg,**kwargs):
            logger.log(level, "calling '%s'(%r,%r)", fn.func_name, arg, kwargs)
            ret=fn(*arg,**kwargs)
            logger.log(level, "called '%s'(%r,%r) got return value: %r", fn.func_name, arg, kwargs, ret)
            return ret
        return _decorated
    return _decorator

@logthis(logging.INFO)
def myfunc(this,that):
    return this+that

Затем, если вы настроите регистратор на вывод в stderr, вы увидите:


>>> logger.setLevel(logging.INFO)
>>> handler=logging.StreamHandler()
>>> logger.addHandler(handler)
>>> myfunc(1,2)
calling 'myfunc'((1, 2),{})
called 'myfunc'((1, 2),{}) got return value: 3

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

Используйте классы декоратора, чтобы сделать ваш отладчик аккуратнее:

class Debugger(object):
    enabled = False
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        if self.enabled:
            print 'Entering', self.func.func_name 
            print '    args:', args, kwargs
        return self.func(*args, **kwargs)

Debugger.enabled = True

@Debugger
def myfunc(a, b, c, d):
    pass

Вот мой декоратор отладки.

      import sys
import time

level = 0

def debug(f):
    def decorated(*args, **kwargs):
        global level
        sys.stderr.write("[debug] %s%s(%s)\n"
            % (' ' * level,
                f.__name__, ", ".join(
                    [str(a) for a in args]
                    + ["%s=%s" % (k, v) for (k, v) in kwargs.items()])))
        level += 1
        t0 = time.time()
        res = f(*args, **kwargs)
        t1 = time.time()
        level -= 1
        sys.stderr.write("[debug] %s= %r (%.9f s)\n"
            % (' ' * level, res, t1 - t0))
        return res
    return decorated

Это улучшение по сравнению с Филом, потому что:

  • он использует stderr, поэтому содержательный вывод сохраняется на stdout,
  • это улучшает видимость рекурсивных вызовов
  • он измеряет продолжительность отлаживаемой функции (не запрошенная, но и не большая помеха)

я использую f.__name__вместо f.func_name, потому что первый больше не работает в python 3.8.

Я понял намек Джона Монтгомери не забывать лечить варгов.

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

Небольшой пример

      @debug
def f(a, b):
    return a + b

@debug
def g(a, b, c):
    return f(a, b) * c

class C:
    @debug
    def m1(self, a, b):
        return a / b

    @classmethod
    @debug
    def m2(c, a, b):
        return b - a

    @staticmethod
    @debug
    def m3(a, b):
        return a * b

if __name__ == '__main__':
    f(1, b=2)
    g(1, 2, 3)
    C().m1(3, 4)
    C.m2(5, 6)
    C.m3(7, 8)

Выход

      $./test.py >/dev/null
[debug] f(1, b=2)
[debug] = 3 (0.000001431 s)
[debug] g(1, 2, 3)
[debug]  f(1, 2)
[debug]  = 3 (0.000000477 s)
[debug] = 9 (0.000024557 s)
[debug] m1(<__main__.C object at 0x7f10a6465d90>, 3, 4)
[debug] = 0.75 (0.000000715 s)
[debug] m2(<class '__main__.C'>, 5, 6)
[debug] = 1 (0.000000477 s)
[debug] m3(7, 8)
[debug] = 56 (0.000000477 s)

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

Вы можете использовать как декоратор для отладки; (@отлаживать)

def debug(func):
    import functools

    @functools.wraps(func)
    def wrapper_debug(*args, **kwargs):
        args_repr = [repr(a) for a in args]  # 1
        kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]  # 2
        signature = ", ".join(args_repr + kwargs_repr)  # 3
        print(f"Call {func.__name__}({signature})")
        value = func(*args, **kwargs)
        print(f"{func.__name__!r} return {value!r}")  # 4
        return value

    return wrapper_debug

Я второе что носкло сказал.

Еще одна вещь, на которую следует обратить внимание: ваша функция немного опасна:

b = myfunc(1,3)

В этом случае "б" Noneпотому что декорированная функция ничего не возвращает.

Другие вопросы по тегам