sys.monitoring — 执行事件监控

3.12 新版功能.


备注

sys.monitoringsys 模块中的一个命名空间,而不是一个独立的模块,因此无需 import sys.monitoring,只需 import sys,然后使用 sys.monitoring

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

程序执行时会发生事件,这些事件可能对监控执行的工具感兴趣。sys.monitoring 命名空间提供了在感兴趣的事件发生时接收回调的方法。

监控 API 由三个组件组成

工具标识符

工具标识符是一个整数及其关联的名称。工具标识符用于阻止工具相互干扰,并允许多个工具同时运行。目前,工具是完全独立的,不能用于相互监控。此限制将来可能会解除。

在注册或激活事件之前,工具应选择一个标识符。标识符是 0 到 5(包括 0 和 5)范围内的整数。

注册和使用工具

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

必须在可以使用 tool_id 之前调用。tool_id 必须在 0 到 5(包括 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(包括 0 和 5)的范围内。

所有 ID 在 VM 中都被视为相同,但为了方便工具协作,预定义了以下 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;参见 STOP_ITERATION 事件

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

这些事件是 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 事件时才可见。

其他事件

其他事件不一定与程序中的特定位置绑定,并且无法单独禁用。

可以监控的其他事件包括

STOP_ITERATION 事件

PEP 380 指定从生成器或协程返回一个值时会引发 StopIteration 异常。然而,这是一种非常低效的返回值方式,因此一些 Python 实现,特别是 CPython 3.12+,除非异常对其他代码可见,否则不会引发异常。

为了允许工具监控真实的异常而不会减慢生成器和协程的速度,提供了 STOP_ITERATION 事件。STOP_ITERATION 可以局部禁用,不像 RAISE

请注意,STOP_ITERATION 事件和 StopIteration 异常的 RAISE 事件是等效的,并且在生成事件时被视为可互换。出于性能原因,实现将优先选择 STOP_ITERATION,但可能会生成带有 StopIterationRAISE 事件。

开启和关闭事件

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

全局设置事件

可以通过修改受监控的事件集来全局控制事件。

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

激活在 event_set 中设置的所有 code 的局部事件。如果 tool_id 未在使用中,则引发 ValueError

禁用事件

sys.monitoring.DISABLE

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

可以通过从回调函数返回 sys.monitoring.DISABLE 来禁用特定代码位置的局部事件。这不会更改已设置的事件或相同事件的任何其他代码位置。

禁用特定位置的事件对于高性能监控非常重要。例如,如果调试器禁用除少数断点之外的所有监控,则程序可以在没有开销的情况下在调试器下运行。

sys.monitoring.restart_events() None

重新启用所有由 sys.monitoring.DISABLE 为所有工具禁用的事件。

注册回调函数

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

为给定的 tool_id 注册 event 的可调用对象 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
    
  • CALLC_RAISEC_RETURNarg0 可以专门是 MISSING

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

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

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

  • RAISERERAISEEXCEPTION_HANDLEDPY_UNWINDPY_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