sys.monitoring --- 执行事件监测

Added in version 3.12.


备注

sys.monitoring is a namespace within the sys module, not an independent module, so there is no need to import sys.monitoring, simply import sys and then use sys.monitoring.

这个命名空间提供了对于激活和控制事件监控所需的函数和常量的访问。

As programs execute, events occur that might be of interest to tools that monitor execution. The sys.monitoring namespace provides means to receive callbacks when events of interest occur.

monitoring API由三个部分组成:

工具标识符

工具标识符是一个整数及其所关联的名称。 工具标识符被用来防止工具之间的相互干扰并允许同时操作多个工作。 目前工具是完全独立的且不能被用于相互监控。 这一限制在将来可能会被取消。

在注册或激活事件之前,工具应选择一个标识符。 标识符是 0 到 5 的开区间内的整数。

注册和使用工具

sys.monitoring.use_tool_id(tool_id: int, name: str, /) None

必须在 tool_id 可被使用之前调用。 tool_id 必须在 0 到 5 的开区间内。 如果 tool_id 已被使用则会引发 ValueError

sys.monitoring.clear_tool_id(tool_id: int, /) None

注销与 tool_id 相关联的所有事件和回调函数。

sys.monitoring.free_tool_id(tool_id: int, /) None

应该在工具不再需要 tool_id 时调用。 在释放 tool_id 之前将调用 clear_tool_id()

sys.monitoring.get_tool(tool_id: int, /) str | None

如果 tool_id 已被使用则返回工具名称,否则返回 Nonetool_id 取值必须在 0 至 5 的开区间内。

虚拟机在处理事件时对所有 ID 都一视同仁,但为便于工具之间的协作而预定义了下列 ID:

sys.monitoring.DEBUGGER_ID = 0
sys.monitoring.COVERAGE_ID = 1
sys.monitoring.PROFILER_ID = 2
sys.monitoring.OPTIMIZER_ID = 5

事件

以下事件是受支持的:

sys.monitoring.events.BRANCH_LEFT

条件分支向左。

由该工具决定如何表示“左”和“右”分支。不能保证哪个分支是“左”哪个分支是“右”,除非它在程序的持续时间内是一致的。

sys.monitoring.events.BRANCH_RIGHT

条件分支向右。

sys.monitoring.events.CALL

Python 代码中的调用(事件发生在调用之前)。

sys.monitoring.events.C_RAISE

从任意可调用对象引发的异常。 Python 函数除外(事件发生在退出之后)。

sys.monitoring.events.C_RETURN

从任意可调用对象返回,Python 函数除外(事件在返回之后发生)。

sys.monitoring.events.EXCEPTION_HANDLED

一个异常被处理。

sys.monitoring.events.INSTRUCTION

一个 VM 指令即将被执行。

sys.monitoring.events.JUMP

在控制流图中进行一次无条件的跳转。

sys.monitoring.events.LINE

一条与之前指令行号不同的指令即将被执行。

sys.monitoring.events.PY_RESUME

恢复执行一个 Python 函数(用于生成器和协程函数),throw() 调用除外。

sys.monitoring.events.PY_RETURN

从一个 Python 函数返回(在返回之前立即发生,被调用方的帧将在栈中)。

sys.monitoring.events.PY_START

开始一个 Python 函数(在调用之后立即发生,被调用方的帧将在栈中)

sys.monitoring.events.PY_THROW

一个 Python 函数由 throw() 调用恢复执行。

sys.monitoring.events.PY_UNWIND

在异常展开期间从一个 Python 函数退出。 这包括在该函数内直接引发的以及被允许继续传播的异常。

sys.monitoring.events.PY_YIELD

从一个 Python 函数产出数据(在产出之前立即发生,被调用方的帧将在栈中)。

sys.monitoring.events.RAISE

一个异常被引发,导致 STOP_ITERATION 事件的异常除外。

sys.monitoring.events.RERAISE

一个异常被重新引发,例如在 finally 代码块结束的时候。

sys.monitoring.events.STOP_ITERATION

一个 StopIteration 被人工引发;参见 the STOP_ITERATION event

将来可能会添加更多事件。

这些事件都是 sys.monitoring.events 命名空间的属性。 每个事件用整数常量的 2 次幂来表示。 要定义一组事件,只需对多个单独事件执行按位或运算即可。 例如,要同时指定 PY_RETURNPY_START 事件,则使用表达式 PY_RETURN | PY_START

sys.monitoring.events.NO_EVENTS

代表 0 的别名以便用户可以这样执行显式比较:

if get_events(DEBUGGER_ID) == NO_EVENTS:
    ...

设置此事件将撤销所有事件的激活。

本地事件

本地事件与程序的正常执行相关联并且发生在明确定义的位置上。 所有本地事件都可以被禁用。 本地事件包括:

已弃用的事件

  • BRANCH

BRANCH 事件已在3.14中被弃用。 使用 BRANCH_LEFTBRANCH_RIGHT 事件可以提供更好的性能,因为它们可以被单独禁用。

辅助事件

辅助事件可以像其他事件一样被监视,但是由另一个事件来控制:

C_RETURNC_RAISE 事件是由 CALL 事件控制的。 C_RETURNC_RAISE 事件只会在相应的 CALL 事件被监控时才能被看到。

其他事件

其他事件不一定与程序中的特定位置相关联并且不能通过 DISABLE 单独禁用。

可以被监视的其他事件包括:

STOP_ITERATION 事件

PEP 380 规定了当从生成器或协程返回值时可引发 StopIteration 异常。 不过,这是一种非常低效的返回值的方式,因此某些 Python 实现,比如 CPython 3.12+,只有在异常对其他代码可见时才会引发它。

为允许工具监视真正的异常而不会拖慢生成器和协程的运行,解释器提供了 STOP_ITERATION 事件。 STOP_ITERATION 可以被局部禁用,这与 RAISE 不同。

请注意,STOP_ITERATION 事件和 StopIteration 异常的 RAISE 事件是等价的,并且在生成事件时被视为可互换的。 出于性能原因,实现将倾向于 STOP_ITERATION,但可能会使用 StopIteration 生成 RAISE 事件。

开启和关闭事件

要监视一个事件,它必须被开启且相应的回调必须被注册。 可以通过将事件设置为全局的和/或针对特定代码对象的来开启或关闭事件。 一个事件将只被触发一次,即使它在全局和局部都被开启。

全局设置事件

通过修改被监视的事件集可以对事件进行全局控制。

sys.monitoring.get_events(tool_id: int, /) int

返回代表所有活动事件的 int

sys.monitoring.set_events(tool_id: int, event_set: int, /) None

激活在 event_set 中设置的所有事件。 如果 tool_id 未被使用则会引发 ValueError

在默认情况下没有被激活的事件。

针对特定代码对象的事件

事件也可以基于每个代码对象来控制。 下面定义的接受一个 types.CodeType 的函数应当准备好接受来自不是在 Python 中定义的类似对象 (参见 监控 C API)。

sys.monitoring.get_local_events(tool_id: int, code: CodeType, /) int

返回 code 的所有 局部事件

sys.monitoring.set_local_events(tool_id: int, code: CodeType, event_set: int, /) None

Activates all the local events for code which are set in event_set. Raises a ValueError if tool_id is not in use.

禁用事件

sys.monitoring.DISABLE

一个可从回调函数返回以禁用当前代码位置上的事件的特殊值。

Local events can be disabled for a specific code location by returning sys.monitoring.DISABLE from a callback function. This does not change which events are set, or any other code locations for the same event.

禁用特定位置的事件对高性能的监控非常重要。 例如,如果调试器禁用了除几个断点外的所有监控那么程序在调试器下运行时就不会产生额外的开销。

If DISABLE is returned by a callback for a global event, ValueError will be raised by the interpreter in a non-specific location (that is, no traceback will be provided).

sys.monitoring.restart_events() None

启用 sys.monitoring.DISABLE 针对所有工具禁用的所有事件。

注册回调函数

sys.monitoring.register_callback(tool_id: int, event: int, func: Callable | None, /) Callable | None

使用给定的 tool_idevent 注册可调用对象 func

如果已经为给定的 tool_idevent 注册了另一个回调,它将被注销并返回。 在其他情况下 register_callback() 将返回 None

引发一个 审计事件 sys.monitoring.register_callback 并附带参数 func

函数可以通过调用 sys.monitoring.register_callback(tool_id, event, None) 来注销。

回调函数可在任何时候被注册或注销。

回调将只被调用一次,即使事件在全局和局部都被开启。 因此,如果一个事件可以被你的代码在全局和局部同时开启那么回调就需要被编写为同时处理两个触发器。

回调函数参数

sys.monitoring.MISSING

一个传给回调函数表明该调用不附带任何参数的特殊值。

当一个激活的事件发生时,已注册的回调函数将被调用。 回调函数返回 DISABLE 以外的对象将没有任何效果。 不同的事件将提供具有不同参数的回调函数,如下所示:

  • PY_STARTPY_RESUME:

    func(code: CodeType, instruction_offset: int) -> object
    
  • PY_RETURNPY_YIELD:

    func(code: CodeType, instruction_offset: int, retval: object) -> object
    
  • CALL, C_RAISEC_RETURN (特别地 arg0 可以为 MISSING):

    func(code: CodeType, instruction_offset: int, callable: object, arg0: object) -> object
    

    code 代表调用所在的代码对象,而 callable 是将要被调用的对象(并因此触发事件)。 如果没有参数,arg0 将被设为 sys.monitoring.MISSING

    对于实例方法,callable 将是在类上找到的 arg0 设为该实例的函数对象(该实例即方法的 self 参数)。

  • RAISE, RERAISE, EXCEPTION_HANDLED, PY_UNWIND, PY_THROWSTOP_ITERATION:

    func(code: CodeType, instruction_offset: int, exception: BaseException) -> object
    
  • LINE:

    func(code: CodeType, line_number: int) -> object
    
  • BRANCH_LEFTBRANCH_RIGHTJUMP:

    func(code: CodeType, instruction_offset: int, destination_offset: int) -> object
    

    注意,destination_offset 是代码下一次执行的地方。

  • INSTRUCTION:

    func(code: CodeType, instruction_offset: int) -> object