结业测验答案(默认折叠)¶
闭卷做完、自己判完再看
点击展开
结业测验答案¶
第三部分的参考实现在同目录
task_bookshelf.py,测试在test_task_bookshelf.py(已在 Python 3.14.4 + Pydantic 2.13 上通过,含内存与 SQLite 两种仓储)。
第一部分¶
- 列表推导式一次性把所有元素放进内存、可反复遍历;生成器按需产出、几乎不占内存,但只能遍历一次。证明:
g = (x for x in range(3)); print(list(g), list(g))→[0, 1, 2] []。 - 闭包在调用时才查外层变量的当前值,不是定义时;三个函数都返回
2。修法:默认参数lambda i=i: i,或用functools.partial/ 工厂函数把值固定住。 __name__、__doc__、__module__、__qualname__、__annotations__、__dict__,并设置__wrapped__。没有它,inspect.signature拿到的是wrapper(*args, **kwargs)——agent 用inspect生成工具 schema 时会全部失效。- 定义
__eq__而不定义__hash__,Python 把__hash__设为None(不可哈希)——相等的对象必须有相同哈希,Python 宁可禁止。frozen=True保证字段不可变,dataclass 于是自动按字段生成__hash__。 Protocol:结构子类型,长得像就算实现,不需要继承;ABC:名义子类型,必须继承并实现抽象方法。工具接口更适合Protocol:任何有name/description/run的对象(函数包装、第三方类)都能直接当工具,不必改它的继承关系。try正常执行;抛异常且匹配 →except;没抛异常 →else;无论如何最后 →finally。finally里return会吞掉正在传播的异常(也覆盖try的返回值);3.14 起(PEP 765)编译器对finally里的return/break/continue发SyntaxWarning。from e设置__cause__,回溯显示"The above exception was the direct cause of…",保留根因;不写则只有隐式__context__(显示"During handling…"),语义是"处理时又出错了"。from None用在故意隐藏内部细节、只给调用方看干净的领域异常时。Optional[int](无默认)= 必填但值可为 None;int | None = None= 可省略,默认 None;Field(default_factory=list)= 可省略,每个实例得到新列表(Pydantic 对普通可变默认值也会拷贝,但default_factory更明确)。model_dump()返回 Python 对象(datetime还是datetime);mode="json"转成 JSON 可表示的类型(ISO 字符串等)。required= 没有默认值的字段(Optional[int]无默认也算必填)。- ≈5 秒(事件循环被阻塞,串行)。修法:① 把端点改成
def(线程池);②await asyncio.to_thread(time.sleep, 1);③ 用真正的异步等待/异步库(await asyncio.sleep(1)、异步驱动)。 gather:一个失败默认立刻把异常抛给调用方,但其余任务继续跑(return_exceptions=True时把异常当结果返回);TaskGroup:一个失败取消其余任务,等它们结束后抛ExceptionGroup。拆法:except* ValueError as eg: for e in eg.exceptions: ...,或eg.split(ValueError)。- 一个进程/一个服务通常建一次(连接池复用),用完
close/with;不设超时可能永远挂起,占满连接与线程。可重试:幂等的 GET/HEAD、5xx、连接/超时类网络错误;不可盲目重试:POST 付款/创建这类非幂等操作,以及 4xx(重试也不会变对)。
第二部分¶
- 防止"在仓库目录里能 import、安装后却不能"的假阳性,并强迫你通过安装(
uv sync可编辑安装)来使用包。直接跑src/kb/cli.py时sys.path[0]是src/kb/,其中没有名为kb的包目录,所以import kb.xxx找不到;应uv run kb或python -m kb。 [project].dependencies是运行这个包必须装的(会随包一起发布给用户);dependency-groups.dev是开发时才要的(pytest、ruff),不进发布物。uv.lock锁定全部传递依赖的精确版本与哈希,让每台机器uv sync得到完全一样的环境。- 库模块只写
logger = logging.getLogger(__name__)并调用它;不要basicConfig、不要加 handler、不要改根 logger 级别(那是应用入口的事)。logger.error(e)只记录消息文本;logger.exception(e)在except块里调用,额外记录完整堆栈(等价于error(..., exc_info=True))。 match只从开头匹配;search在任意位置找第一个;fullmatch要求整串匹配。.*贪婪尽可能长,.*?懒惰尽可能短。不该用正则:解析 HTML/JSON/CSV 等有结构的格式(用解析器)、简单前后缀/包含判断(用str方法)、需要可读性和可维护性时。- 参数化让数据永远是"值"而不是 SQL 文本,杜绝注入且自动处理引号;
with conn:在块结束时commit、块内抛异常时rollback(注意它不会关闭连接);IF NOT EXISTS让建表幂等,程序每次启动都能安全执行。 patch替换的是"名字在某个命名空间里的绑定",被测代码在哪个模块里查这个名字,就要打到那个模块(from x import y会复制绑定)。yield之后的代码在使用该 fixture 的测试结束后执行(清理),包括测试失败时。覆盖率 100% 说明每行都被执行过,不说明有断言、不说明边界与错误路径被验证。- 让业务规则可以在 CLI、测试、脚本、其他协议里复用,也让测试不需要起 HTTP;领域异常统一在
main.py的exception_handler里映射(NotFound→404 等)。对 agent 项目的直接好处:工具层、编排层、存储层同样可以不依赖任何 LLM SDK 或 Web 框架,换模型、换接口、离线测试都不动核心逻辑。 - 参考方向(以你的审查表为准):
kb import增加文件数/大小上限与路径校验;kb search --regex限制模式长度并捕获re.error;aggregator--base-url只允许http/https并拒绝明显内网/file://;导出路径resolve()后限制在工作目录内;日志脱敏。"不需要修"的例子:桩服务器只监听127.0.0.1且无鉴权——它是本地开发工具,不对外暴露,加鉴权反而增加演示成本,但要在 README 明确写出"不要对外监听"。
第三部分¶
见 task_bookshelf.py(领域 dataclass + 异常层次、BookIn Pydantic 校验、BookRepository Protocol、内存与 SQLite 两种实现、Bookshelf 服务)与 test_task_bookshelf.py(8 个测试,参数化同时覆盖两种仓储)。评分对照:
| 项 | 检查点 |
|---|---|
| 领域与异常(2) | 基类 + 两个子类;Book 是 frozen dataclass,tags 是 tuple |
| Pydantic 校验(3) | pattern、min_length/max_length、max_length=5、extra="forbid"、标签小写去重的 field_validator |
| Protocol + 实现(2) | 协议方法签名完整;内存实现分配自增 id |
| 服务逻辑(1) | 先校验再查重;added_at 带 UTC 时区 |
| 测试(2) | 至少覆盖正常、非法输入、重复 ISBN、不存在 id |