python 3.12 中 sys.monitoring 默认编译禁用,需源码配置 --with-monitoring 重新构建;启用后通过 register/set_events 注册回调监听字节码事件,call 事件开销低于 sys.setprofile 但仍有性能影响。

sys.monitoring 在 Python 3.12 中默认不可用
Python 3.12 确实引入了 sys.monitoring,但它在标准构建中是**编译时禁用的**——除非你手动启用 --with-monitoring 编译选项并重新构建 CPython。直接运行 import sys; sys.monitoring 会抛出 AttributeError。这不是安装问题,也不是版本没到,而是官方默认关闭该功能以控制 ABI 稳定性和发布风险。
如果你只是想试用,必须从源码编译:
- 克隆
https://github.com/python/cpython,切换到3.12分支 - 配置时加上
./configure --with-monitoring -
make -j并make install
注意:这样生成的解释器不能混用预编译的 wheel(如 numpy),且不兼容部分 C 扩展——因为监控钩子会修改字节码执行路径。
启用后如何注册事件监听器(比如函数调用)
sys.monitoring 不是装饰器或上下文管理器,它是一组全局、低层级的 C API 绑定,通过 sys.monitoring.set_events 和回调函数配合使用。核心限制是:每个事件类型(如 sys.monitoring.events.CALL)在同一时刻只能有一个激活的监听器。
典型用法如下:
import sys
if not hasattr(sys, 'monitoring'):
raise RuntimeError("sys.monitoring not available")
<p>def on_call(code, instruction):
print(f"CALL at {code.co_name}:{instruction}")</p><h1>注册监听器(ID 必须是整数,建议用模块名哈希或固定小整数)</h1><p>sys.monitoring.register(123, sys.monitoring.events.CALL, on_call)
sys.monitoring.set_events(123, sys.monitoring.events.CALL)
</p>
关键点:
SkillSub Pro - Python 题解与代码注释双功能技能功能概述SkillSub Pro - Python 题解与代码注释双功能技能是一项面向实际任务的技能,主要用于SkillSub Pro 是一个 Python 题解生成与代码注释的 双功能合体技能 ,专为学生、算法学习者和开发者设计;✅ 一个技能,两种用途 :;核心要点📝 题解模式 :输入题目/题号,自动生成完整 Python 题解(含详细注释、解题思路、复杂度分析);💬 注释模式 :输入 Python 代码,自动添加详细中。它将相关步骤、
- ID 是任意整数,但冲突会导致覆盖——多个监控工具共存时需协调 ID 分配
-
set_events是开关,不是注册动作;注册靠register,启用靠set_events - 回调函数签名固定为
(code: CodeType, instruction: int),instruction是字节码偏移,不是行号
为什么 CALL 事件开销比 sys.setprofile 低?
sys.monitoring 的设计目标就是绕过 Python 层的帧对象创建和检查。它在字节码解释循环内直接触发 C 回调,不经过 Python 调用栈,也不构造 frame 对象。相比之下,sys.setprofile 每次函数进入/退出都会产生一个完整的 frame 对象,并调用 Python 可调用对象,开销高一个数量级。
但“低开销”是相对的:
- 哪怕空回调(只 return),也会让热点路径变慢约 5–10%,取决于 CPU 分支预测效果
- 如果回调里做字符串格式化、日志写入或任何 Python 对象操作,开销立刻回升到
setprofile水平 - 仅适用于 C 扩展开发者或极简埋点场景(例如统计某几个关键函数被调了多少次)
常见错误:事件没触发,或触发后立即报错
最常遇到的是事件注册后完全静默,或回调中访问 code.co_name 报 SystemError: unexpected opcode。原因很具体:
- Python 3.12.0 的
CALL事件只在CALL字节码执行前触发,但此时当前帧可能尚未完全初始化——code对象有效,但某些属性(如f_locals)还不可读 - 未调用
sys.monitoring.set_events(id, event)就以为注册即生效;必须显式开启 - 监听器 ID 被其他库(如某些调试器原型)占用,导致你的
register被悄悄覆盖 - 在多线程环境中注册监听器,但事件只在主线程解释器循环中触发(目前无跨线程支持)
验证是否生效的最快方式:sys.monitoring.get_events(id) 应返回非零值;否则说明没开启。
实际部署前务必确认:你真需要字节码级精度,还是用 functools.wraps + __call__ 钩子更可控、更可测、也更容易关掉。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










