跳转到主要内容
AI Coding / 开发

代码调研 · 摸清陌生项目架构

两轮法读懂大型仓库:先喂骨架出架构地图,再按点名文件补全文出调用链。

免责声明:本页展示内容均为 AI 模型生成,仅供参考,不构成任何效果承诺;实际产出效果与 Credits 消耗以平台用量统计为准。
本篇以 CPython 3.13 的 asyncio 包(33 个模块 / 约 1.5 万行 / 519 KB)跑通两轮调研法:接手陌生仓库最费时的是先搞清它怎么跑起来,真实项目动辄几万行,一次塞不进去也不该塞,正确做法是先给结构骨架让模型指路、点名关键文件,再按它点名的文件补全文,第一轮输入只要十分之一。 调用链:qwen3.8-max(两轮调用)。(1) 只喂签名骨架(52 KB,约为原码 10%)让它出架构地图并点名文件 → (2) 按点名的文件喂全文,出精确调用链。

步骤一:只给骨架,出架构地图并点名文件

ast 遍历 *.py、只取类与函数签名和 docstring 首行、丢掉函数体,生成签名骨架,连同下面的指令一起发给 qwen3.8-max
我接手维护一个 Python 项目,需要先摸清它的架构。这个包有 33 个模块、约 1.5 万行代码,太大没法一次全给你,所以我先给你**结构骨架**(只有类、函数签名和 docstring 首行,没有实现体)。

请基于骨架完成三件事:

一、架构分层
这个包在设计上分成哪几层?各层职责是什么?层与层之间靠什么协作?画一个简明的层次图(文字版)。

二、核心抽象与继承关系
找出这个包最重要的 5-8 个抽象(基类/协议/接口),说明:
- 每个抽象解决什么问题
- 它的主要子类有哪些、按什么维度分化(按平台?按 IO 机制?)
- 用缩进树的形式画出关键的继承关系

三、我该按什么顺序读,以及现在就要看哪几个文件
- 给一个阅读顺序(先读哪个模块、为什么)
- 然后**明确点名 3-5 个文件**,说你需要看它们的完整实现才能讲清核心运行机制。用这个格式单独列一行:
  NEED_FILES: a.py, b.py, c.py

要求:
- 类名、方法名必须与骨架一致,不要臆造
- 你只看到了签名,凡是靠推测得出的结论请标注"(推测)"
- 不要复述骨架内容,要给出你的归纳

骨架如下:
【粘贴用 ast 生成的签名骨架】

步骤二:按点名文件补全文,出调用链

把模型点名的文件全文发回,让它基于真实代码画出核心调用链:
好,下面是你点名的文件的完整实现。请基于真实代码回答:

一、一条核心流程的完整调用链
挑「用 asyncio.run() 跑一个协程直到结束」这条最常用的路径,画出从入口到协程真正被执行的完整调用链。要求:
- 用真实的函数/方法名,标注它属于哪个类、哪个文件
- 每一步说明它做了什么关键动作
- 特别说清:事件循环是在哪一步真正开始转的、协程是怎么被包装成可调度对象的

二、事件循环的一次迭代里发生了什么
事件循环跑一圈,依次做哪几件事?按真实代码里的顺序列出,并指出这些动作分别在哪个方法里。

三、三个容易踩的实现细节
基于你读到的真实代码,指出三个使用者容易误解、但代码里其实有明确处理的点。每条要指明对应的代码位置。

要求:所有函数名、类名、属性名必须与给你的代码完全一致;凡是代码里没看到、靠常识补的,明确标注"(未在本次给出的文件中确认)"。

【粘贴它点名的那几个文件的全文】
【二、核心抽象与继承关系(轮 1 · 据骨架归纳)】

  1) 事件循环 EventLoop —— 整个包的中枢,负责调度与 IO
       AbstractEventLoop(events.py,抽象接口)
         `- BaseEventLoop(base_events.py,通用实现)
             `- BaseSelectorEventLoop(selector_events.py,基于 selector 的 IO)
                 `- _UnixSelectorEventLoop(unix_events.py,Unix 平台)
             `- BaseProactorEventLoop(proactor_events.py,Windows IOCP)
       —— 按「平台 + IO 机制」两个维度分化

  2) Future / Task —— 把「未来才有的结果」和「协程的执行」统一成可等待对象
       Future(futures.py)
         `- Task(tasks.py,把协程驱动起来的那一层)

  3) Transport / Protocol —— 一对搭档:Transport 管「怎么传字节」,
       Protocol 管「收到字节做什么」,二者通过回调解耦

【一、asyncio.run() 跑一个协程直到结束的调用链(轮 2 · 据真实代码)】

  asyncio.run(main)                              # runners.py(本轮未给,标注未确认)
    `- BaseEventLoop.run_until_complete(future)   # base_events.py
        |- self._check_closed()                       确认循环未关闭
        |- self._check_running()                      确认没有别的循环在跑
        |- new_task = not futures.isfuture(future)    判断是否已是 Future
        |- future = tasks.ensure_future(future,…)     <-- 协程在这一步被包装成 Task
        |- future._log_destroy_pending = False        抑制 destroy pending 警告
        |- future.add_done_callback(                  目标完成后停循环
        |     _run_until_complete_cb)
        |- self.run_forever()                         <-- 事件循环从这里真正开始转
        `- return future.result()                     正常返回/重抛异常/抛 CancelledError

【三、三个容易踩的实现细节(模型主动指出)】
  · Future = _CFuture = _asyncio.Future(futures.py 末尾)——运行时可能走 C 实现,
    纯 Python 版仅在没有 C 扩展时作为回退
  · call_soon 注册的回调不会立即执行,而是排到事件循环的下一轮
  · 协程被包装成 Task 的时机是 ensure_future,不是 run 的那一刻

说明

  • 消耗:本玩法涉及长上下文 + 深度思考(qwen3.8-max)。两轮实测用量:轮 1 输入约 1.4 万 / 输出约 2.0 万(其中思考约 1.2 万);轮 2 输入约 7.0 万 / 输出约 2.3 万(思考约 1.5 万)。思考消耗普遍接近甚至超过正文输出,估算成本别只看报告字数。
  • 两轮法省在哪:519 KB 源码直接进上下文约合十几万 token,而先喂 52 KB 骨架只花了约 1.4 万输入,第一轮点名文件后再补全文,比一次全塞省得多。
  • 敏感信息:代码进上下文意味着内容会发送到服务端,含密钥、内网地址、客户数据的文件请先剔除或脱敏。