# `sys.monitoring` --- 執行事件監控

在 3.12 版被加入.

備註

`sys.monitoring` 是 `sys` 模組內的一個命名空間，不是一個獨立的模組，所以不需要 `import sys.monitoring`，只需 `import sys` 然後使用 `sys.monitoring`。

此命名空間提供對啟動和控制事件監控所需的函式和常數的存取。

當程式執行時，會發生一些能被監控工具關注的事件。`sys.monitoring` 命名空間提供了在發生欲關注事件時接收回呼的方法。

監控 API 由三個元件組成：

- 工具識別器

- 事件

- 回呼 (callbacks)

## 工具識別器

工具識別器是一個整數和關聯的名稱。工具識別器用於防止工具相互干擾並允許多個工具同時運作。目前工具是完全獨立的，不能用來互相監控。將來此限制可能會取消。

在註冊或啟動事件之前，工具應選擇一個識別器。識別器是 0 到 5（含）範圍內的整數。

### 註冊和使用工具

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

  必須在使用 tool_id 之前呼叫。tool_id 必須在 0 到 5（含）範圍內。如果 tool_id 正在使用，則引發 `ValueError`。

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

  一旦工具不再需要 tool_id 就應該被呼叫。

備註

`free_tool_id()` 不會停用與 tool_id 相關的全域或區域事件，也不會取消註冊任何回呼函式。這個函式只是用來通知虛擬機器 (VM) 不再使用特定的 tool_id。

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

  如果 tool_id 正在使用，則回傳該工具的名稱，否則回傳 `None`。tool_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

  採取（或不採取）一個條件分支。

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

  虛擬機器指令即將被執行。

sys.monitoring.events.JUMP

  在控制流程圖中進行無條件跳轉。

sys.monitoring.events.LINE

  即將執行的指令與前一條指令的列號不同。

sys.monitoring.events.PY_RESUME

  Python 函式的繼續執行（對於產生器和協程函式），除了 `throw()` 呼叫。

sys.monitoring.events.PY_RETURN

  從 Python 函式回傳（發生在即將回傳之前，被呼叫者的 frame 將位於堆疊上）。

sys.monitoring.events.PY_START

  Python 函式的開始（在呼叫後立即發生，被呼叫者的 frame 將位於堆疊上）

sys.monitoring.events.PY_THROW

  Python 函式透過 `throw()` 呼叫來繼續。

sys.monitoring.events.PY_UNWIND

  在例外展開 (unwind) 期間從 Python 函式退出。

sys.monitoring.events.PY_YIELD

  來自 Python 函式的 yield（在即將 yield 之前發生，被呼叫者的 frame 將位於堆疊上）。

sys.monitoring.events.RAISE

  例外被引發，除了那些會導致 `STOP_ITERATION` 的事件外。

sys.monitoring.events.RERAISE

  例外被重新引發 (re-raise)，例如在 `finally` 區塊的最後。

sys.monitoring.events.STOP_ITERATION

  一個人為的 `StopIteration` 被引發；請參閱 STOP_ITERATION 事件。

將來可能會新增更多事件。

這些事件是 `sys.monitoring.events` 命名空間的屬性。每個事件以 2 次方的整數常數表示。要定義一組事件，只要將個別事件以 bitwise OR 相接即可。例如，若要指定 `PY_RETURN` 和 `PY_START` 兩個事件，請使用運算式 `PY_RETURN | PY_START`。

sys.monitoring.events.NO_EVENTS

  `0` 的別名，以便使用者可以進行明確比較，例如：

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

事件被分為三組：

### 區域事件

區域事件與程式的正常執行相關，並發生在明確定義的位置。所有區域事件都可以被停用。區域事件有：

- `PY_START`

- `PY_RESUME`

- `PY_RETURN`

- `PY_YIELD`

- `CALL`

- `LINE`

- `INSTRUCTION`

- `JUMP`

- `BRANCH`

- `STOP_ITERATION`

### 附屬事件 (ancillary events)

附屬事件可以像其他事件一樣被監控，但由另一個事件所控制：

- `C_RAISE`

- `C_RETURN`

`C_RETURN` 和 `C_RAISE` 事件由 `CALL` 事件控制。只有當對應的 `CALL` 事件受到監控時，才會看到 `C_RETURN` 和 `C_RAISE` 事件。

### 其他事件

Other events are not necessarily tied to a specific location in the program and cannot be individually disabled via `DISABLE`.

其他可以監控的事件有：

- `PY_THROW`

- `PY_UNWIND`

- `RAISE`

- `EXCEPTION_HANDLED`

### STOP_ITERATION 事件

PEP 380 指出從產生器或協程回傳值時要引發 `StopIteration` 例外。然而，這是一種非常低效的回傳值方式，因此有一些 Python 實作（特別是 CPython 3.12+）不會引發例外，除非它對其他程式碼是可見的。

為了允許工具去監控真正的例外而不減慢產生器和協程的速度，提供了 `STOP_ITERATION` 事件。與 `RAISE` 不同，`STOP_ITERATION` 可以區域停用。

## 開啟和關閉事件

為了監控一個事件，必須打開它並註冊相應的回呼。可以透過將事件設定為全域或只為特定程式碼物件來開啟或關閉事件。

### 全域設定事件

可以透過修改正在監控的事件集合來全域地控制事件。

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

  Returns all the local events for 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.

停用特定位置的事件對於高效能監控非常重要。舉例來說，如果除少數斷點外，偵錯器可以停用所有監控，那麼在偵錯器下執行程式就不會有任何開銷 (overhead)。

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_id 來註冊對 event 的可呼叫物件 func

  如果給定的 tool_id 和 event 已經註冊了另一個回呼，則會取消註冊並回傳。否則 `register_callback()` 會回傳 `None`。

  以 `func` 引數引發一個稽核事件 (auditing event) `sys.monitoring.register_callback`。

可以透過呼叫 `sys.monitoring.register_callback(tool_id, event, None)` 來取消註冊函式。

回呼函式可以隨時被註冊和取消註冊。

### 回呼函式引數

sys.monitoring.MISSING

  傳遞給回呼函式的特殊值，表示該呼叫沒有引數。

當有效事件發生時，已註冊的回呼函式會被呼叫。不同的事件會為回呼函式提供不同的引數，如下所示：

- `PY_START` 和 `PY_RESUME`：

      func(code: CodeType, instruction_offset: int) -> DISABLE | Any

- `PY_RETURN` 和 `PY_YIELD`：

      func(code: CodeType, instruction_offset: int, retval: object) -> DISABLE | Any

- `CALL`、`C_RAISE` 和 `C_RETURN`：

      func(code: CodeType, instruction_offset: int, callable: object, arg0: object | MISSING) -> DISABLE | Any

  如果沒有引數，arg0 將被設定為 `sys.monitoring.MISSING`。

- `RAISE`、`RERAISE`、`EXCEPTION_HANDLED`、`PY_UNWIND`、`PY_THROW` 和 `STOP_ITERATION`：

      func(code: CodeType, instruction_offset: int, exception: BaseException) -> DISABLE | Any

- `LINE`：

      func(code: CodeType, line_number: int) -> DISABLE | Any

- `BRANCH` 和 `JUMP`：

      func(code: CodeType, instruction_offset: int, destination_offset: int) -> DISABLE | Any

  請注意，destination_offset 是程式碼接下來要執行的地方。對於未採用的分支，這將是該分支之後的指令的偏移量。

- `INSTRUCTION`：

      func(code: CodeType, instruction_offset: int) -> DISABLE | Any