装饰器逐行记录功能执行

Sin*_*ion 10 python logging

我正在编写一个需要几分钟才能运行的脚本,并希望向用户提供有关其进度的一些输出.不幸的是,我非常懒惰.我想要做的是编写一个没有日志记录的函数,然后应用一个装饰器,它会逐步执行该函数并在执行该行之前打印每一行.基本上我正在寻找的是loggingdecorator这样的:

>>> @loggingdecorator
... def myfunction():
...     foo()
...     bar()
...     baz()
>>> myfunction()
Starting myfunction
foo() ... [OK]
bar() ... [OK]
baz() ... [OK]
myfunction Done!
Run Code Online (Sandbox Code Playgroud)

这是我到目前为止所尝试的:

import sys


def logging_tracer(frame, event, arg):
    def local_tracer(local_frame, event, arg):
        if frame is local_frame:
            print frame.f_code.co_name, event, arg

    print frame.f_code.co_name, event, arg
    return local_tracer


def loggingdecorator(func):
    def _wrapper():
        old_trace_function = sys.gettrace()
        sys.settrace(logging_tracer)
        try:
            result = func()
        except:
            raise
        else:
            return result
        finally:
            sys.settrace(old_trace_function)
    return _wrapper
Run Code Online (Sandbox Code Playgroud)

不幸的是,这打印得太多了; 它跟随函数调用并逐行打印出来(好吧,这实际上不打印源代码行,现有的答案使用inspect,结合跟踪函数中的框架对象上的东西就可以了),但是我有点难过,logging_tracer除非有问题的功能实际装饰.

Sin*_*ion 5

这就是我的想法。 @Corley Brigman的评论让我开始朝着正确的方向前进。这有点 hackey,sys.gettrace/settrace相当明显地记录为“CPython 实现细节”,因此不应期望该解决方案适用于其他实现。也就是说,它似乎效果很好。cpython 中的跟踪功能不提供任何“行已完成执行”的通知,因此[ok]我的问题实际上没有任何意义。

修复递归跟踪问题只是保留已修饰函数的缓存,然后仅在被跟踪的帧来自已修饰函数时才生成输出。

import inspect
import sys


def logging_tracer(frame, event, arg):
    lines, firstline = inspect.getsourcelines(frame)

    def local_tracer(local_frame, event, arg):
        if event == 'line' and frame is local_frame:
            print event, frame.f_lineno,'\t', lines[frame.f_lineno - firstline]
            #print event, lines[frame.f_lineno - firstline]
            #print frame.f_code.co_name, frame.f_lineno, event, arg

    if frame.f_code in LOG_THESE_FUNCTIONS:
        print event, frame.f_lineno,'\t', lines[frame.f_lineno - firstline + (event == 'call')]
        #print frame.f_code.co_name, event, arg
        return local_tracer
    else:
        return None


LOG_THESE_FUNCTIONS = set()


def loggingdecorator(func):
    LOG_THESE_FUNCTIONS.add(func.func_code)

    def _wrapper():
        old_trace_function = sys.gettrace()
        sys.settrace(logging_tracer)
        try:
            result = func()
        except:
            raise
        else:
            return result
        finally:
            sys.settrace(old_trace_function)
    return _wrapper
Run Code Online (Sandbox Code Playgroud)