跳转至

结业测验答案(默认折叠)

闭卷做完、自己判完再看

点击展开

结业测验答案

第三部分的参考实现在同目录 task_bookshelf.py,测试在 test_task_bookshelf.py(已在 Python 3.14.4 + Pydantic 2.13 上通过,含内存与 SQLite 两种仓储)。

第一部分

  1. 列表推导式一次性把所有元素放进内存、可反复遍历;生成器按需产出、几乎不占内存,但只能遍历一次。证明:g = (x for x in range(3)); print(list(g), list(g))[0, 1, 2] []
  2. 闭包在调用时才查外层变量的当前值,不是定义时;三个函数都返回 2。修法:默认参数 lambda i=i: i,或用 functools.partial / 工厂函数把值固定住。
  3. __name____doc____module____qualname____annotations____dict__,并设置 __wrapped__。没有它,inspect.signature 拿到的是 wrapper(*args, **kwargs)——agent 用 inspect 生成工具 schema 时会全部失效。
  4. 定义 __eq__ 而不定义 __hash__,Python 把 __hash__ 设为 None(不可哈希)——相等的对象必须有相同哈希,Python 宁可禁止。frozen=True 保证字段不可变,dataclass 于是自动按字段生成 __hash__
  5. Protocol:结构子类型,长得像就算实现,不需要继承;ABC:名义子类型,必须继承并实现抽象方法。工具接口更适合 Protocol:任何有 name/description/run 的对象(函数包装、第三方类)都能直接当工具,不必改它的继承关系。
  6. try 正常执行;抛异常且匹配 → except;没抛异常 → else;无论如何最后 → finallyfinallyreturn 会吞掉正在传播的异常(也覆盖 try 的返回值);3.14 起(PEP 765)编译器对 finally 里的 return/break/continueSyntaxWarning
  7. from e 设置 __cause__,回溯显示"The above exception was the direct cause of…",保留根因;不写则只有隐式 __context__(显示"During handling…"),语义是"处理时又出错了"。from None 用在故意隐藏内部细节、只给调用方看干净的领域异常时。
  8. Optional[int](无默认)= 必填但值可为 None;int | None = None = 可省略,默认 None;Field(default_factory=list) = 可省略,每个实例得到新列表(Pydantic 对普通可变默认值也会拷贝,但 default_factory 更明确)。
  9. model_dump() 返回 Python 对象(datetime 还是 datetime);mode="json" 转成 JSON 可表示的类型(ISO 字符串等)。required = 没有默认值的字段(Optional[int] 无默认也算必填)。
  10. ≈5 秒(事件循环被阻塞,串行)。修法:① 把端点改成 def(线程池);② await asyncio.to_thread(time.sleep, 1);③ 用真正的异步等待/异步库(await asyncio.sleep(1)、异步驱动)。
  11. gather:一个失败默认立刻把异常抛给调用方,但其余任务继续跑return_exceptions=True 时把异常当结果返回);TaskGroup:一个失败取消其余任务,等它们结束后抛 ExceptionGroup。拆法:except* ValueError as eg: for e in eg.exceptions: ...,或 eg.split(ValueError)
  12. 一个进程/一个服务通常建一次(连接池复用),用完 close/with;不设超时可能永远挂起,占满连接与线程。可重试:幂等的 GET/HEAD、5xx、连接/超时类网络错误;不可盲目重试:POST 付款/创建这类非幂等操作,以及 4xx(重试也不会变对)。

第二部分

  1. 防止"在仓库目录里能 import、安装后却不能"的假阳性,并强迫你通过安装(uv sync 可编辑安装)来使用包。直接跑 src/kb/cli.pysys.path[0]src/kb/,其中没有名为 kb 的包目录,所以 import kb.xxx 找不到;应 uv run kbpython -m kb
  2. [project].dependencies运行这个包必须装的(会随包一起发布给用户);dependency-groups.dev 是开发时才要的(pytest、ruff),不进发布物。uv.lock 锁定全部传递依赖的精确版本与哈希,让每台机器 uv sync 得到完全一样的环境。
  3. 库模块只写 logger = logging.getLogger(__name__) 并调用它;不要 basicConfig、不要加 handler、不要改根 logger 级别(那是应用入口的事)。logger.error(e) 只记录消息文本;logger.exception(e)except 块里调用,额外记录完整堆栈(等价于 error(..., exc_info=True))。
  4. match 只从开头匹配;search 在任意位置找第一个;fullmatch 要求整串匹配。.* 贪婪尽可能长,.*? 懒惰尽可能短。不该用正则:解析 HTML/JSON/CSV 等有结构的格式(用解析器)、简单前后缀/包含判断(用 str 方法)、需要可读性和可维护性时。
  5. 参数化让数据永远是"值"而不是 SQL 文本,杜绝注入且自动处理引号;with conn: 在块结束时 commit、块内抛异常时 rollback(注意它不会关闭连接);IF NOT EXISTS 让建表幂等,程序每次启动都能安全执行。
  6. patch 替换的是"名字在某个命名空间里的绑定",被测代码在哪个模块里查这个名字,就要打到那个模块(from x import y 会复制绑定)。yield 之后的代码在使用该 fixture 的测试结束后执行(清理),包括测试失败时。覆盖率 100% 说明每行都被执行过,不说明有断言、不说明边界与错误路径被验证。
  7. 让业务规则可以在 CLI、测试、脚本、其他协议里复用,也让测试不需要起 HTTP;领域异常统一在 main.pyexception_handler 里映射(NotFound→404 等)。对 agent 项目的直接好处:工具层、编排层、存储层同样可以不依赖任何 LLM SDK 或 Web 框架,换模型、换接口、离线测试都不动核心逻辑。
  8. 参考方向(以你的审查表为准):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) 基类 + 两个子类;Bookfrozen dataclass,tags 是 tuple
Pydantic 校验(3) patternmin_length/max_lengthmax_length=5extra="forbid"、标签小写去重的 field_validator
Protocol + 实现(2) 协议方法签名完整;内存实现分配自增 id
服务逻辑(1) 先校验再查重;added_at 带 UTC 时区
测试(2) 至少覆盖正常、非法输入、重复 ISBN、不存在 id