我有一个庞大的代码库,其中包含数千个函数。
我想在每个函数调用之前和之后、函数何时开始和结束时启用代码执行。
有没有办法在不重新编译 Python 或向每个函数中添加代码的情况下做到这一点?有没有办法 Hook 我的代码中的每个函数调用?
最佳答案
是的,您可以使用 sys.settrace()
或 sys.setprofile()
注册回调和处理函数'call'
也许'return'
事件。但是,这会大大减慢您的代码速度。调用函数有开销,为每个函数调用添加另一个函数调用会增加更多开销。
默认情况下,sys.settrace()
hook 仅在调用时调用(其中 call 表示正在输入一个新范围,包括类主体和列表、dict 和集合理解以及生成器表达式),但您可以选择返回一个跟踪函数以针对刚刚输入的范围调用.如果您只对通话感兴趣,则只需返回 None
从跟踪功能。请注意,这使您可以选择收集更多信息的范围。 sys.settrace()
仅报告 Python 代码,不报告内置可调用对象或已编译扩展中定义的可调用对象。sys.setprofile()
对 Python 函数和内置函数以及编译的扩展对象的调用都会调用 hook,并且每当调用返回或引发异常时,也会调用相同的回调。不幸的是,无法区分返回 None
的 Python 函数。或引发异常。
在这两种情况下,您都会得到当前的 frame ,以及事件名称和 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
禁用每行事件(需要 Python 3.7 或更高版本)。这是两个钩子(Hook)的简短演示(假设使用 Python 3.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
如果可以更改代码,请仅将装饰器添加到您要跟踪的函数中,这样您就可以限制开销。如果您准备编写一些代码来进行更改,您甚至可以自动执行此操作;与
ast
module您可以将代码解析为可以转换的对象树,包括添加 @decorator
句法。如果您的代码库很大,这并不是那么简单,但确实值得。见Green Tree Snakes project有关如何执行此操作的更深入的文档。
关于python - 在 Python 中 Hook 每个函数调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59088671/