Python Profile工具拾遗中提及的line_profiler一直让自己很好奇,它是如何得到每一条语句的执行时间的呢?于是去翻看了其源码。

line_profiler的核心代码为_line_profiler.pyx,简单浏览之后发现其核心在于如下调用,

PyEval_SetTrace(python_trace_callback, self)

settrace这个函数在脚本层的什么地方呢?答案是sys模块,直接引用官方文档中settrace函数的定义,取与line_profiler相关的参数定义,

sys.settrace(tracefunc)

Trace functions should have three arguments: frame, event, and arg. frame is the current stack frame. event is a string: 'call', 'line', 'return', 'exception', 'c_call', 'c_return', or 'c_exception'. arg depends on the event type.

...

'line'
The interpreter is about to execute a new line of code or re-execute the condition of a loop. The local trace function is called; arg is None; the return value specifies the new local trace function. See Objects/lnotab_notes.txt for a detailed explanation of how this works.

'return'
A function (or other code block) is about to return. The local trace function is called; arg is the value that will be returned, or None if the event is caused by an exception being raised. The trace function’s return value is ignored.

...

根据这个定义描述,我们来实现一个最简单的line相关trace,记录每一个语句的触发时间,

import sys


def trace_cb(frame, event, arg):
    code = frame.f_code
    if event == 'line':
        print datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), code.co_filename, frame.f_lineno
    return trace_cb


def foobar():
    a = 1
    b = 2
    return a + b


sys.settrace(trace_cb)
foobar()
sys.settrace(None)

实现一个完整的line_profiler还是有很多地方需要注意的,但上述这个简单的示例应该可以说是给出了大体方向。

在了解了这些之后愈发觉得自己这样实在是太后知后觉了,即便曾经翻看过sys.settrace的定义描述,但完全没想到利用它能够实现写什么。虽说类似objgraph、line_profiler之类的库代码都比较短,阅读学习之后发现其实现也未必困难,但为什么自己没有想到呢?只能说还是能力的欠缺、意识的匮乏,以及见识的浅薄。