挂钩 Python 中的每个函数调用
Hooking every function call in Python
我有一个庞大的代码库,其中包含数千个函数。
我想在每次函数调用前后、函数开始和结束时启用代码执行。
有没有办法不用重新编译Python,或者不给每个函数添加代码?有没有办法挂钩我代码中的每个函数调用?
是的,您可以使用 sys.settrace()
or sys.setprofile()
函数来注册回调并处理 'call'
和 'return'
事件。但是,这会大大降低您的代码速度。调用函数有开销,为每个函数调用添加另一个函数调用会增加更多开销。
默认情况下,sys.settrace()
挂钩仅在调用时被调用(其中 call 表示正在进入一个新范围,包括 class 正文和列表, dict 和 set comprehensions,以及生成器表达式),但您可以选择 return 一个要为刚输入的范围调用的跟踪函数。如果您只对调用感兴趣,那么只需 return None
来自跟踪函数。请注意,这使您可以选择收集更多信息的范围。 sys.settrace()
仅报告 Python 代码,不报告内置可调用文件或已编译扩展中定义的可调用文件。
调用 sys.setprofile()
挂钩来调用 Python 函数和内置函数以及编译的扩展对象,并且每当调用 returns 或异常时也会调用相同的回调被提出。不幸的是,无法区分 Python 函数 returning None
或引发异常。
在这两种情况下,您都会得到当前 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()
return 函数对象而不是 None
时,您可以跟踪框架内的其他事件,这就是 'local' 跟踪函数。您可以为此重复使用相同的函数对象。这会减慢速度,因为现在您要为每一行源代码调用一个函数。然后为 'line'
、'exception'
和 'return'
事件调用本地跟踪函数,但您可以通过设置 frame.f_trace_lines = False
禁用每行事件(需要 Python 3.7或更新版本)。
这是两个钩子的简短演示(假设使用 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 you can parse code into object trees that can be transformed, including adding in @decorator
syntax. This isn't that simple but really worth it if your codebase is large. See the Green Tree Snakes project 获取有关如何执行此操作的更深入的文档。
我有一个庞大的代码库,其中包含数千个函数。
我想在每次函数调用前后、函数开始和结束时启用代码执行。
有没有办法不用重新编译Python,或者不给每个函数添加代码?有没有办法挂钩我代码中的每个函数调用?
是的,您可以使用 sys.settrace()
or sys.setprofile()
函数来注册回调并处理 'call'
和 'return'
事件。但是,这会大大降低您的代码速度。调用函数有开销,为每个函数调用添加另一个函数调用会增加更多开销。
默认情况下,sys.settrace()
挂钩仅在调用时被调用(其中 call 表示正在进入一个新范围,包括 class 正文和列表, dict 和 set comprehensions,以及生成器表达式),但您可以选择 return 一个要为刚输入的范围调用的跟踪函数。如果您只对调用感兴趣,那么只需 return None
来自跟踪函数。请注意,这使您可以选择收集更多信息的范围。 sys.settrace()
仅报告 Python 代码,不报告内置可调用文件或已编译扩展中定义的可调用文件。
调用 sys.setprofile()
挂钩来调用 Python 函数和内置函数以及编译的扩展对象,并且每当调用 returns 或异常时也会调用相同的回调被提出。不幸的是,无法区分 Python 函数 returning None
或引发异常。
在这两种情况下,您都会得到当前 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()
return 函数对象而不是 None
时,您可以跟踪框架内的其他事件,这就是 'local' 跟踪函数。您可以为此重复使用相同的函数对象。这会减慢速度,因为现在您要为每一行源代码调用一个函数。然后为 'line'
、'exception'
和 'return'
事件调用本地跟踪函数,但您可以通过设置 frame.f_trace_lines = False
禁用每行事件(需要 Python 3.7或更新版本)。
这是两个钩子的简短演示(假设使用 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 you can parse code into object trees that can be transformed, including adding in @decorator
syntax. This isn't that simple but really worth it if your codebase is large. See the Green Tree Snakes project 获取有关如何执行此操作的更深入的文档。