ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

深入 CPython 的 sys.monitoring:PEP 669 执行事件监控 API 完全指南

深入 CPython 的 sys.monitoring:PEP 669 执行事件监控 API 完全指南 深入 CPython 的 sys.monitoringPEP 669 执行事件监控 API 完全指南【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython本篇技术指南以 CPython 官方文档 Doc/library/sys.monitoring.rst 为核心骨架系统讲解 PEP 669 落地到 CPython 3.12 的执行事件监控Execution Event Monitoring体系工具标识符Tool ID、事件分类、全局与逐代码对象per code object的事件开关、回调注册与各事件签名并结合仓库源码Python/instrumentation.c、Include/internal/pycore_instruments.h等剖析其字节码插桩与停止整个世界Stop The World再插桩的实现原理。读完本文你将能基于sys.monitoring从零编写一套调试器、覆盖率统计或轻量级 Profiler并理解它为何比旧的sys.settrace/sys.setprofile更适合高性能监控场景。一、认识 sys.monitoring它是命名空间而非模块sys.monitoring是 CPython 3.12PEP 669起随解释器内置提供的执行事件监控接口用于在程序运行过程中接收回调从而观察函数调用、返回、行号推进、分支跳转与异常等执行细节。首先必须澄清一个最容易踩的坑sys.monitoring是sys模块内部的一个命名空间对象而不是一个可独立导入的模块。直接执行import sys.monitoring # ModuleNotFoundError: No module named sys.monitoring会抛出ModuleNotFoundError。正确用法是导入sys后访问其属性import sys sys.monitoring # 模块对象m_name 为 sys.monitoring从实现上看该对象由 Python/instrumentation.c 中的PyModuleDef monitoring_module定义模块名被命名为sys.monitoring再通过_Py_CreateMonitoringObject()创建并挂载到sys模块的属性上。整个监控 API 由三个核心部件构成工具标识符Tool identifiers整数编号 关联名称用于让多个监控工具并行协作而不互相干扰事件Eventssys.monitoring.events命名空间下的一组事件常量回调Callbacks为某个工具、某个事件注册的回调函数事件发生时由虚拟机触发。这三个部件对应文档原文的Tool identifiers、Events与Callbacks三节下面逐一展开。二、工具标识符Tool ID让多个监控工具和平共处2.1 为什么需要 Tool ID当调试器、覆盖率工具、Profiler、JIT 优化器等多个工具同时希望监听执行事件时如果它们共用一套回调就会相互踩踏。sys.monitoring用05 共 6 个整数 Tool ID把各工具隔离每个工具申请一个 ID各工具拥有自己独立的事件集合与回调表。文档同时说明目前工具之间是完全独立的一个工具不能监控另一个工具的行为即不能用监控 API 观察其他工具注册了什么这一限制未来可能放开。2.2 预定义的 ID 常量虽然虚拟机对 05 的所有 ID 一视同仁但 PEP 669 预定义了几个 ID 以便生态协作sys.monitoring.DEBUGGER_ID 0 # 调试器 sys.monitoring.COVERAGE_ID 1 # 覆盖率工具 sys.monitoring.PROFILER_ID 2 # Profiler sys.monitoring.OPTIMIZER_ID 5 # JIT 优化器这些常量在 Include/cpython/monitoring.h 之外另有对应内部宏PY_MONITORING_DEBUGGER_ID 0、PY_MONITORING_COVERAGE_ID 1、PY_MONITORING_PROFILER_ID 2、PY_MONITORING_OPTIMIZER_ID 5定义于 Include/internal/pycore_instruments.h。注意这个内部头文件还预留了PY_MONITORING_SYS_PROFILE_ID 6与PY_MONITORING_SYS_TRACE_ID 7——它们是解释器内部用来支撑旧版sys.setprofile()/sys.settrace()的工具位详见后文与 sys.settrace/sys.setprofile 的关系面向用户的可用 ID 只有 05。Tool ID 的合法性校验位于check_valid_tool()Python/instrumentation.c越界会抛出ValueError: invalid tool %d (must be between 0 and 5)。2.3 工具生命周期管理 API一个工具在使用前必须先注册 ID使用结束后释放。官方提供 4 个函数函数说明异常/边界use_tool_id(tool_id: int, name: str, /) - None在tool_id使用前必须调用name为工具名tool_id必须在 0~5若该 ID 已被占用则抛ValueErrorclear_tool_id(tool_id: int, /) - None注销与tool_id关联的全部事件与回调函数需先注册过该 IDfree_tool_id(tool_id: int, /) - None工具不再需要该 ID 时调用内部先调用clear_tool_id再释放 ID需先注册过该 IDget_tool(tool_id: int, /) - str \| None返回占用该 ID 的工具名若无人使用返回Nonetool_id必须在 0~5参数末尾的/表示这些函数只支持位置参数不支持关键字传参。以use_tool_id为例Python/instrumentation.c 的实现细节是工具名必须是str否则抛ValueError: tool name must be a str且monitoring_tool_names[tool_id]已非空时抛ValueError: tool %d is already in use名称被存储在解释器状态PyInterpreterState.monitoring_tool_names数组中。free_tool_idL2237-L2254则会先_PyMonitoring_ClearToolId清空事件与回调再Py_CLEAR释放名称引用。典型的工具启动/收尾模式import sys MY_TOOL 4 sys.monitoring.use_tool_id(MY_TOOL, MyTool.Tracer) assert sys.monitoring.get_tool(MY_TOOL) MyTool.Tracer # ... 注册事件与回调开始工作 ... sys.monitoring.free_tool_id(MY_TOOL) # 收尾等价于 clear 释放这也是仓库测试 Lib/test/test_monitoring.py 的规范用法——该测试在每个用例的tearDown中都会调用sys.monitoring.free_tool_id(TEST_TOOL)保证测试间互不污染其test_tool用例就断言了get_tool返回注册时传入的名称。三、事件体系events 命名空间与事件分类3.1 事件的三种定位与事件常量监控 API 支持 18 种执行事件外加 1 个已弃用事件。它们都是sys.monitoring.events命名空间的属性每个事件是一个 2 的幂整数常量因此事件集合可以直接用按位或组合例如同时要PY_RETURN与PY_START就写PY_RETURN | PY_START。此外还提供sys.monitoring.events.NO_EVENTS # 0 的别名便于显式比较NO_EVENTS的典型用法是判断当前没有任何事件在监听if sys.monitoring.get_events(sys.monitoring.DEBUGGER_ID) sys.monitoring.events.NO_EVENTS: ... # 该工具当前未激活任何事件把NO_EVENTS即 0设为事件集合等价于关闭全部事件。事件常量的构造逻辑在_Py_CreateMonitoringObject()Python/instrumentation.c先创建一个types.SimpleNamespace类型的命名空间对象再用add_power2_constant()按1 i依次填入各事件最后挂上NO_EVENTS 0。从 Include/cpython/monitoring.h 可以看到事件编号的完整布局序号即1 序号的幂次事件编号宏常量对应 events 属性分类0~10PY_MONITORING_EVENT_PY_START...PY_MONITORING_EVENT_STOP_ITERATIONPY_START...STOP_ITERATION局部事件Local可逐位置关闭11~15RAISE/EXCEPTION_HANDLED/PY_UNWIND/PY_THROW/RERAISE同左其他事件Other主要面向异常16~17C_RETURN/C_RAISE同左附属事件Ancillary由 CALL 控制18BRANCH同左已弃用3.14 起3.2 局部事件Local Events局部事件与程序的正常执行绑定发生在明确的位置上因而可以针对某个具体代码位置单独禁用。共 11 种PY_STARTPython 函数开始发生在调用之后立刻被调方 frame 已在栈上PY_RESUMEPython 函数被恢复执行——针对生成器与协程函数不含throw()调用PY_RETURNPython 函数返回发生在 return 之前的一瞬间被调方 frame 仍在栈上PY_YIELDPython 函数产出值yield 之前触发被调方 frame 仍在栈上CALLPython 代码中的一次调用发生在调用之前LINE即将执行一条与前一条指令行号不同的指令INSTRUCTION一条虚拟机指令即将被执行JUMP控制流图中发生一次无条件跳转BRANCH_LEFT条件分支走向左BRANCH_RIGHT条件分支走向右STOP_ITERATION人为抛出的StopIteration见 3.5 专门说明关于左/右分支文档强调没有任何保证哪一边是左哪一边是右唯一保证是整个程序运行期间方向保持一致具体如何向用户呈现左右完全由工具决定。3.3 附属事件Ancillary Events与已弃用事件C_RAISE从任意可调用对象抛出的异常Python 函数除外在退出后发生与C_RETURN从任意可调用对象返回Python 函数除外在返回后发生虽然可以被监听但受控于CALL事件只有对应位置的CALL事件处于被监听状态时才会看到C_RETURN/C_RAISE。这一约束在源码层强制执行——set_eventsPython/instrumentation.c和set_local_events都会检查若事件集合包含C_RETURN_EVENTS却不包含C_CALL_EVENTS直接抛ValueError: cannot set C_RETURN or C_RAISE events independently随后还会把C_RETURN_EVENTS从集合中剥离仅作CALL的附随产物。已弃用事件BRANCH该事件在 3.14 起被弃用。文档给出的理由是改用BRANCH_LEFT/BRANCH_RIGHT会获得更好的性能因为它们能被独立地按位置禁用。源码侧也保留了兼容处理set_events/set_local_events收到BRANCH位时会将其清除并自动展开成BRANCH_RIGHT | BRANCH_LEFTL2367-L2370。3.4 其他事件Other Events与位置解耦另有一类事件不与程序中的某个特定位置强绑定无法针对单个代码位置单独禁用RAISE异常被抛出会导致STOP_ITERATION事件的除外RERAISE异常被重新抛出例如finally块结束时的隐式 re-raiseEXCEPTION_HANDLED某个异常被处理PY_THROWPython 函数通过throw()调用恢复执行PY_UNWINDPython 函数在异常展开unwinding期间退出包括函数内部直接抛出并放任继续传播的异常3.15 的行为变化文档标注versionchanged:: 3.15——其他事件现在也可以按整个 code object 粒度开关了回调返回DISABLE会为整个 code object针对当前工具禁用该事件。仓库当前处于开发分支Include/internal/pycore_instruments.h 头文件注释也印证了这一点Other events. These can now be turned on and disabled on a per code object basis.3.5 STOP_ITERATION 事件的前因后果PEP 380 规定生成器或协程返回一个值时会抛出一个StopIteration异常。但用抛异常来传返回值非常低效因此 CPython 3.12 的实现除非该异常会对其他代码可见否则不再真的抛异常。为了让工具能监控到真实的异常又不必拖慢生成器/协程sys.monitoring提供了可局部禁用的STOP_ITERATION事件。需要特别强调的是STOP_ITERATION事件与针对StopIteration异常的RAISE事件在语义上是等价的生成事件时二者可以互换。实现出于性能考虑会优先产生STOP_ITERATION但也可能对某个StopIteration产生RAISE事件——所以工具若要统计真正的StopIteration应当同时对这两种事件都做好准备。3.6 未来扩展文档明确未来可能增加更多事件事件编号存在天然扩展空间内部事件总数上限为_PY_MONITORING_EVENTS共 19 个位见 pycore_instruments.h。四、开关事件全局、按 code object、DISABLE 与重启一个事件要被触发需要同时满足① 事件已打开② 已注册对应回调。事件开关分两层全局对整个解释器与逐 code object局部。如果一个事件同时在全局和局部都打开它仍然只会触发一次。默认情况下没有任何事件处于激活状态。4.1 全局事件开关sys.monitoring.get_events(tool_id: int, /) - int # 返回该工具所有已激活事件组成的 int sys.monitoring.set_events(tool_id: int, event_set: int, /) - Noneset_events会激活event_set中置位的全部事件若tool_id未注册使用不在monitoring_tool_names中抛ValueErrorevent_set超出合法事件范围 1 19或非法组合也会抛ValueError。实现上set_eventsPython/instrumentation.c内部会先做合法性检查与BRANCH/附属事件归一化然后调用_PyEval_StopTheWorld(interp)暂停所有线程、执行_PyMonitoring_SetEvents()完成真正的字节码插桩、再_PyEval_StartTheWorld(interp)恢复运行。全局活动工具集合存放在解释器状态的interp-monitors_Py_GlobalMonitors结构每个事件一个字节记录哪些工具在位中。4.2 按 code object 开关局部事件sys.monitoring.get_local_events(tool_id: int, code: CodeType, /) - int sys.monitoring.set_local_events(tool_id: int, code: CodeType, event_set: int, /) - None这两个函数只针对局部事件生效需要传入一个types.CodeType对象一般从函数/方法的__code__属性取得。文档特别提示凡是接受CodeType的函数都应能接受来自非 Python 定义的函数的外观相似对象——这与 Doc/c-api/monitoring.rst 描述的 C API 约定一致C 侧事件触发接口接受codelike对象。源码中get_local_events/set_local_events都先做PyCode_Check(code)类型校验不满足则抛TypeError: code must be a code object。set_local_events同样执行StopTheWorld_PyMonitoring_SetLocalEvents()的重插桩流程L2456-L2459。每个 code object 的局部监控状态存于其_co_monitoring字段指向的_PyCoMonitoringData结构pycore_instruments.h中其中local_monitors/active_monitors记录各工具请求监听与实际生效的局部事件tools/line_tools/per_instruction_tools按 code unit 记录这个位置要通知哪些工具是实现逐位置禁用的数据基础tool_versions[PY_MONITORING_TOOL_IDS]记录各工具插桩时的版本号用于增量去插桩/重插桩per_instruction_opcodes指令级事件需要保存的原始操作码。从源码结构可以推断LINE、INSTRUCTION事件拥有独立的按 code unit 的数据通道因此它们的按位置禁用开销可以被压缩到每 code unit 一个字节级别这正是高性能监控的根基。4.3 从回调中返回 DISABLE按位置精准关闭DISABLE是sys.monitoring模块层面的一个特殊单例值源码中为_PyInstrumentation_DISABLE随模块初始化挂载见 L2574。它只能在回调函数中作为返回值使用对局部事件返回DISABLE会禁用当前这个代码位置的该事件。它不会改动设置了哪些事件也不影响同事件的其他代码位置对其他事件3.15 起返回DISABLE会按整个 code object维度禁用该事件针对当前工具。文档着重指出按位置禁用对高性能监控至关重要。例如调试器可以让程序在除了少数几个断点之外全部禁用监控的状态下近乎零开销地运行只有真正命中断点位置时才产生事件。4.4 restart_events()全局重新启用sys.monitoring.restart_events() - Nonerestart_events会为所有工具重新启用所有曾被DISABLE关闭的事件。其实现Python/instrumentation.c比较精巧利用版本号机制——每次设置/重启事件都会递增一个全局版本号而每个 code object 记录着上次按什么版本插的桩restart_events会把last_restart_version提升到一个中间值、再推进全局版本随后调用instrument_all_executing_code_objects()让所有正在执行的 code object 重新按新版本插桩从而复活被禁用的位置。若版本号溢出则抛OverflowError: events set too many times。五、注册回调register_callback 与各类事件签名5.1 注册与注销sys.monitoring.register_callback(tool_id: int, event: int, func: Callable | None, /) - Callable | None为tool_id与event注册回调func如果该工具/事件之前已有回调旧回调会被注销并作为返回值返回否则返回None注销回调只需传funcNonesys.monitoring.register_callback(tool_id, event, None)回调可以在任意时刻注册或注销甚至可以在另一个回调触发过程中进行若同一事件在全局与局部都打开了回调只被调用一次因此工具的代码需要能同时处理两种触发来源。源码中的校验逻辑Python/instrumentation.ccheck_valid_tooltool_id 必须在 0~5_Py_popcount32(event) ! 1event 必须是单个事件2 的幂一次注册多个事件会抛ValueError: The callback can only be set for one event at a timeevent 编号越界抛ValueError: invalid event %d触发审计钩子PySys_Audit(sys.monitoring.register_callback, O, func)——即注册回调会发出sys.monitoring.register_callback审计事件便于安全工具审计func is None时内部转为NULL完成注销。5.2 MISSING表达调用没有参数MISSING是另一个特殊单例值_PyInstrumentation_MISSING专门用于CALL/C_RAISE/C_RETURN事件中表示该调用没有参数。CALL事件回调的第四个参数arg0仅在确有参数时才是真实值若调用没有参数arg0就是MISSING。5.3 各类事件的回调签名总表回调的返回值除了DISABLE外返回任何其他对象都不会产生效果。不同事件传给回调的参数不同官方文档的完整契约如下CodeType即types.CodeTypeinstruction_offset是指令偏移量事件回调签名要点说明PY_START,PY_RESUMEfunc(code: CodeType, instruction_offset: int) - objectPY_RETURN,PY_YIELDfunc(code: CodeType, instruction_offset: int, retval: object) - object携带返回值CALL,C_RAISE,C_RETURNfunc(code: CodeType, instruction_offset: int, callable: object, arg0: object) - objectarg0可为MISSINGcode是发起调用处的 code objectcallable是即将被调用的对象RAISE,RERAISE,EXCEPTION_HANDLED,PY_UNWIND,PY_THROW,STOP_ITERATIONfunc(code: CodeType, instruction_offset: int, exception: BaseException) - object携带异常对象LINEfunc(code: CodeType, line_number: int) - object注意第二参是行号而非指令偏移BRANCH_LEFT,BRANCH_RIGHT,JUMPfunc(code: CodeType, instruction_offset: int, destination_offset: int) - objectdestination_offset是下一步将要执行的位置INSTRUCTIONfunc(code: CodeType, instruction_offset: int) - object关于CALL事件文档给了两条关键语义CALL的code表示正在发起调用的那个 code objectcallable才是触发了该事件的、即将被调用的对象对于实例方法callable会是从类上找到的函数对象而arg0被设为实例本身即方法的self参数。5.4 完整示例一个最小可用的调用监控器综合上面所有 API一个监控Python 函数调用并统计返回的最小工具如下import sys TID 3 # 自己选一个 0~5 之间未被占用的 ID if sys.monitoring.get_tool(TID) is None: sys.monitoring.use_tool_id(TID, call.tracer) calls {} def on_py_start(code, offset): calls[code] calls.get(code, 0) 1 def on_call(code, offset, callable, arg0): arg no-args if arg0 is sys.monitoring.MISSING else repr(arg0) print(fCALL {callable.__qualname__} at {code.co_filename}:{offset} arg0{arg}) sys.monitoring.register_callback(TID, sys.monitoring.events.PY_START, on_py_start) sys.monitoring.register_callback(TID, sys.monitoring.events.CALL, on_call) # 需要同时监听 CALL 才能收到 C_RETURN/C_RAISE附属事件规则 def on_c_return(code, offset, callable, retval): print(fC_RETURN {callable.__qualname__} - {retval!r}) sys.monitoring.register_callback(TID, sys.monitoring.events.C_RETURN, on_c_return) # 打开事件全局打开对所有函数生效 sys.monitoring.set_events( TID, sys.monitoring.events.PY_START | sys.monitoring.events.CALL | sys.monitoring.events.C_RETURN, # CALL 已包含合法 ) def demo(x): return x * 2 demo(21) print(PY_START count:, calls[demo.__code__]) # 收尾清理 sys.monitoring.free_tool_id(TID)5.5 只监控某个函数set_local_events 用法如果只想精确监控某一个函数例如只在函数foo上设事件使用set_local_eventssys.monitoring.set_local_events( TID, foo.__code__, sys.monitoring.events.LINE | sys.monitoring.events.PY_RETURN, ) def on_line(code, lineno): print(fline {lineno}) def on_return(code, offset, retval): print(freturned {retval!r}) return sys.monitoring.DISABLE # 该位置只触发一次随后按位置关闭在回调中返回sys.monitoring.DISABLE即可实现文档所说的断点式精准监控定位到目标行后立刻关闭该位置的事件把额外开销降到最低。六、底层原理从事件到字节码插桩6.1 本地事件需要字节码插桩sys.monitoring并不是简单地在解释器主循环里查一张事件→回调大表。对PY_START、LINE、CALL、JUMP、BRANCH_*这类局部事件事件与具体指令绑定因此需要在字节码层面插桩instrumentationVM 会改写 code object 中的指令例如把一条指令替换为INSTRUMENTED_LINE之类的特殊指令并保存原始操作码让指令执行到该位置时去查询哪个工具监听、该调哪个回调。关键数据路径是 Include/internal/pycore_instruments.h 中定义的_PyCoMonitoringData挂载在PyCodeObject._co_monitoring上以及_Py_LocalMonitors/_Py_GlobalMonitors每事件用一个字节的位图记录哪些工具在位即工具集。由于每事件 8 个工具位可用 1 个uint8_t表达判断某个事件是否有工具监听只需一次内存读与一次按位与这是它能够低开销运行的结构基础。由于插桩会改写正在执行的字节码必须先停止所有线程再改。因此set_events/set_local_events/restart_events乃至启用sys.settrace时源码都遵循同一范式_PyEval_StopTheWorld(interp) # 1. 停止世界保证无线程正在执行被改代码 ... 修改插桩 / 更新版本号 ... _PyEval_StartTheWorld(interp) # 2. 恢复运行见 Python/instrumentation.c 的set_events实现。6.2 事件触发与 C 层 Fire 接口当某个被监控位置真正执行时VM 通过_Py_call_instrumentation*系列内部函数pycore_instruments.h把事件分发给对应工具的回调。面向扩展模块作者CPython 在 3.13 起提供了C 级监控 API完整文档见 Doc/c-api/monitoring.rst头文件声明位于 Include/cpython/monitoring.h每个事件对应一个PyMonitoring_FireXxxEvent(...)接口如PyMonitoring_FirePyStartEvent、PyMonitoring_FireLineEvent、PyMonitoring_FireCallEvent用于扩展在模拟 Python 代码执行时主动触发监控事件这些函数接收一个PyMonitoringState结构封装事件的激活状态以及事件参数codelikeCodeType或模拟它的对象、指令偏移以及部分事件特有的参数VM 在触发事件时会自动禁用 tracing用户代码无需自己处理重入问题调用监控函数时不应处于异常已设置状态文档明确列出的少数与当前异常配合工作的接口除外所有 Fire 函数成功返回 0、出错返回 -1 并设置异常注意监控 API 目前没有受限 APILimited APImonitoring.h顶部注释写明#ifndef Py_LIMITED_API守护。6.3 与 sys.settrace / sys.setprofile 的关系sys.monitoring并未完全取代旧机制——从 pycore_instruments.h 可以看出解释器内部把sys.settrace和sys.setprofile映射为两个保留工具 ID6 与 7通过 Python/legacy_tracing.c 把旧 API 转译到监控框架上。也就是说新旧两套追踪体系底层共用同一套监控/插桩机制且解释器利用 0~5 之外预留的这两个位使旧 API 与sys.monitoring用户工具互不干扰。6.4 与其他文档/测试的交叉印证仓库中与sys.monitoring相关的第一手资料还包括官方 API 参考本文主题Doc/library/sys.monitoring.rstC 级扩展 API适用于任何想生成事件而非仅仅消费事件的扩展模块见 Doc/c-api/monitoring.rst功能测试Lib/test/test_monitoring.py 覆盖了 Tool 生命周期、各事件触发次数、DISABLE/MISSING语义等如MonitoringBasicTest、MonitoringCountTestLib/test/test_free_threading/test_monitoring.py 覆盖自由线程free-threaded构建下的监控行为C API 测试样例Modules/_testcapi/monitoring.c。七、实践要点与最佳实践小结回顾文档与源码落地一个sys.monitoring工具时应遵循以下要点命名空间不可导入统一import sys后用sys.monitoring/sys.monitoring.eventsID 即契约0~5 任选优先使用预定义常量调试器用DEBUGGER_ID、覆盖率用COVERAGE_ID、Profiler 用PROFILER_IDuse_tool_id注册、free_tool_id释放、get_tool探测占用同一 ID 不能重复注册会抛ValueError事件集合用按位或所有 events 常量都是 2 的幂NO_EVENTS(0) 用于显式关闭与空集比较先开事件、再等回调事件未打开则回调不会被触发全局与局部都打开也只触发一次回调需容忍两类来源CALL 附属规则想收C_RETURN/C_RAISE必须同时监听CALL否则set_events会直接抛ValueError按位置禁用是性能关键局部事件在回调中返回DISABLE即关掉当前位置实现除断点外零开销restart_events()可全局复活所有被禁位置3.15 起其他事件也可按整个 code object 禁用不要用旧 BRANCH3.14 起已弃用用BRANCH_LEFT/BRANCH_RIGHT替代性能更好且可独立禁用统计 StopIteration 需双保险STOP_ITERATION与携带StopIteration的RAISE语义等价、产生时互换二者都应处理区分 code 与 callableCALL系列事件里第一参 code 是调用发生处的 code objectcallable才是被调用对象无参数调用时arg0 MISSING实例方法调用时arg0即self、callable是类上的函数对象扩展模块请用 C API需要主动产生事件时参考 Doc/c-api/monitoring.rst 的PyMonitoring_Fire*系列注意目前无 Limited API 版本。sys.monitoring让 CPython 首次拥有了一套面向并行多工具、可按 code object 与代码位置精准裁剪的官方监控通道。理解其 Tool ID 隔离、事件分类与DISABLE/MISSING契约后无论是实现断点调试器、轻量级覆盖率统计还是自研采样 Profiler你都有了比sys.settrace更精确、更可控的底层接口。【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表