我有一个大型的代码库,它由数千个函数组成。
我希望在每个函数调用之前和之后,在函数启动和结束时启用代码执行。
有没有一种不需要重新编译Python或向每个函数添加代码的方法?我的代码中的每个函数调用都有挂钩的方法吗?
发布于 2019-11-28 04:25:08
是的,您可以使用sys.settrace()
或sys.setprofile()
函数注册回调,并处理'call'
和'return'
事件。但是,这会大大降低代码的速度。调用一个函数有开销,为每个函数调用添加另一个函数调用会增加更多的开销。
默认情况下,sys.settrace()
钩子只对调用进行调用(其中调用指示正在输入的新范围,包括类主体和列表、dict和set理解以及生成器表达式),但您也可以为刚刚输入的作用域返回要调用的跟踪函数。如果您只对调用感兴趣,那么只需从跟踪函数返回None
。请注意,这使您可以选择收集更多信息的范围。sys.settrace()
只报告Python代码,而不是内置调用或编译扩展中定义的调用。
sys.setprofile()
钩子用于调用Python、builtins和已编译的扩展对象,每当调用返回或引发异常时,也会调用相同的回调。不幸的是,无法区分返回None
的Python函数还是引发异常的Python函数。
在这两种情况下,您都会得到当前的框架,以及事件名称和arg
,通常设置为None
,但对于某些事件,则设置为更具体的内容:
def call_tracer(frame, event, arg):
# called for every new scope, event = 'call', arg = None
# frame is a frame object, not a function!
print(f"Entering: {frame.f_code.co_name}")
return None
sys.settrace(call_tracer)
当使用sys.settrace()
返回函数对象而不是None
时,可以跟踪框架内的其他事件,这是“本地”跟踪函数。您可以为此重用相同的函数对象。这会使事情更慢,因为现在您正在为每一行源代码调用一个函数。然后对'line'
、'exception'
和'return'
事件调用本地跟踪函数,但是可以通过设置frame.f_trace_lines = False
(需要Python3.7或更高版本)来禁用逐行事件。
下面是这两个挂钩的简短演示(假设使用Python3.7或更高版本);它忽略了异常事件选项:
import sys
# demo functions, making calls and returning things
def foo(bar, baz):
return bar(baz)
def spam(name):
print(f"Hello, {name}")
return [42 * i for i in range(17)]
# trace functions, one only call events, another combining calls and returns
def call_tracer(frame, event, arg):
# called for every new scope, event = 'call', arg = None
# frame is a frame object, not a function!
print(f"Entering: {frame.f_code.co_name}")
return None
def call_and_return_tracer(frame, event, arg):
if event == 'call':
print(f"Entering: {frame.f_code.co_name}")
# for this new frame, only trace exceptions and returns
frame.f_trace_lines = False
return call_and_return_tracer
elif event == 'c_call':
print(f"Entering: {arg.__name__}")
elif event == 'return':
print(f"Returning: {arg!r}")
elif event == 'c_return':
print(f"Returning from: {arg.__name__}")
if __name__ == '__main__':
sys.settrace(call_tracer)
foo(spam, "world")
print()
sys.settrace(call_and_return_tracer)
foo(spam, "universe")
print()
sys.settrace(None)
sys.setprofile(call_and_return_tracer)
foo(spam, "profile")
运行此输出:
Entering: foo
Entering: spam
Hello, world
Entering: <listcomp>
Entering: foo
Entering: spam
Hello, universe
Entering: <listcomp>
Returning: [0, 42, 84, 126, 168, 210, 252, 294, 336, 378, 420, 462, 504, 546, 588, 630, 672]
Returning: [0, 42, 84, 126, 168, 210, 252, 294, 336, 378, 420, 462, 504, 546, 588, 630, 672]
Returning: [0, 42, 84, 126, 168, 210, 252, 294, 336, 378, 420, 462, 504, 546, 588, 630, 672]
Entering: foo
Entering: spam
Entering: print
Hello, profile
Returning from: print
Entering: <listcomp>
Returning: [0, 42, 84, 126, 168, 210, 252, 294, 336, 378, 420, 462, 504, 546, 588, 630, 672]
Returning: [0, 42, 84, 126, 168, 210, 252, 294, 336, 378, 420, 462, 504, 546, 588, 630, 672]
Returning: [0, 42, 84, 126, 168, 210, 252, 294, 336, 378, 420, 462, 504, 546, 588, 630, 672]
Returning: None
如果可能的话,只在要跟踪的函数中添加修饰器,这样就可以限制开销。如果您准备编写一些代码来进行更改,甚至可以将其自动化;使用模块,您可以将代码解析为可以转换的对象树,包括添加@decorator
语法。这并不是那么简单,但如果您的代码库很大的话,这是非常值得的。有关如何做到这一点的更多深入文档,请参见绿树蛇项目。
https://stackoverflow.com/questions/59088671
复制