跳转至

第 2 周周测答案(默认折叠)

闭卷做完、自己判完再看

点击展开

第 2 周周测答案

所有输出已在 Python 3.14.4 + Pydantic 2.13 上实际运行核对。

一、预测输出

  1. 3 int[] —— lax 模式把 "3" 转成 int;Pydantic 会为每个实例拷贝默认值,a.tagsb.tags 不是同一个列表(和 dataclass 的可变默认参数相反)。
  2. 2 [('name',), ('qty',)] —— 两个字段各一条错误;extra=1 默认被忽略。要让它报错:model_config = ConfigDict(extra="forbid")
  3. ['name', 'qty'] 0 —— 有默认值的 tags 不在 required 里;gt=0 映射成 JSON Schema 的 exclusiveMinimum: 0
  4. {'name': 'a', 'qty': 2, 'tags': []}。含 datetime 时:model_dump() 保留 datetime 对象(json.dumps 会失败),mode="json" 转成 ISO 字符串。
  5. 2026 {'y': '2026', 'm': '09'} ['2026-09', '2026-10'] —— search 只返回第一个匹配;findall 返回全部。
  6. X X2b None —— .* 贪婪吃到最后一个 b.*? 懒惰到第一个 bre.match 只从字符串开头匹配,"ab" 开头不是 b
  7. kb.service root 0 30 —— 点号分层:父 logger 是 root;自身 level 0(NOTSET)表示"继承";有效级别继承 root 的默认 WARNING(30)。
  8. 1 —— with conn: 在块内抛异常时回滚,第二条插入被撤销;第一条已提交。
  9. 第一行 {"\u540d": "\u503c"},第二行 {"名": "值"} —— 默认 ensure_ascii=True 把非 ASCII 转义。
  10. [1, 2] [3, 4] —— TypeAdapter 不用建模型也能校验/转换任意类型,validate_json 直接吃 JSON 文本。

二、找 bug

  1. SQL 字符串拼接 → 注入 + 引号崩溃(标题里有 ' 就报错)。改为参数化:execute("... WHERE title LIKE ?", (f"%{pattern}%",))
  2. 库模块顶部 basicConfig 抢走了根 logger 的配置权:谁 import 了这个模块,全局日志就被改成 DEBUG。库里只写 logger = logging.getLogger(__name__),配置只在程序入口做一次。
  3. 密钥写进代码默认值 → 进入 Git 历史与网站/包;.env 被提交更是直接泄露。默认值应为 str(无默认,强制配置)或从环境读取;.env.gitignore,仓库只放 .env.example;已提交的密钥要作废重发。
  4. ① 没写 encoding="utf-8":中文 Windows 用 GBK 写,别处读会乱码/报错;② 中文变 \uXXXX(缺 ensure_ascii=False);③ 非原子:写到一半崩溃留下半个损坏文件。正确:写同目录 .tmpos.replace(tmp, path)
  5. Optional[str] 只表示"值可以是 None",字段仍是必填(没有默认值)。要可省略必须给默认:body: str | None = None

三、写代码(参考)

16.

from datetime import datetime
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator

class Task(BaseModel):
    model_config = ConfigDict(str_strip_whitespace=True)
    title: str = Field(min_length=1, max_length=50)
    priority: Literal["low", "high"] = "low"
    tags: list[str] = Field(default_factory=list)
    due: datetime | None = None

    @field_validator("tags")
    @classmethod
    def _norm(cls, tags: list[str]) -> list[str]:
        return list(dict.fromkeys(t.strip().lower() for t in tags if t.strip()))

    @model_validator(mode="after")
    def _high_needs_due(self):
        if self.priority == "high" and self.due is None:
            raise ValueError("high 优先级必须有 due")
        return self
17.
import logging
from pathlib import Path

def setup_logging(level: str, log_file: Path | None = None) -> None:
    root = logging.getLogger()
    root.setLevel(level.upper())
    if root.handlers:          # 已配置过:只调级别,不重复加 handler
        return
    fmt = logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s")
    console = logging.StreamHandler(); console.setFormatter(fmt); root.addHandler(console)
    if log_file:
        fh = logging.FileHandler(log_file, encoding="utf-8"); fh.setFormatter(fmt); root.addHandler(fh)
库代码不该调用它:日志配置是"应用"的决定(输出到哪、什么级别),库被谁 import 就不该替谁做决定,否则多个库互相覆盖、日志重复打印。 18.
import re
LOG_LINE = re.compile(r"""
    ^(?P<time>\d{4}-\d{2}-\d{2}\ \d{2}:\d{2}:\d{2})\s+
    \[(?P<level>[A-Z]+)\]\s+
    (?P<logger>[\w.]+):\s+
    (?P<message>.*)$
""", re.VERBOSE)

def parse_log_line(line: str) -> dict | None:
    m = LOG_LINE.match(line.strip())
    return m.groupdict() if m else None
19.
import sqlite3

SCHEMA = """
CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL);
CREATE INDEX IF NOT EXISTS idx_notes_title ON notes(title);
"""

def migrate(conn: sqlite3.Connection) -> None:
    with conn:
        conn.executescript(SCHEMA)     # IF NOT EXISTS → 幂等

class SqliteRepo:
    def __init__(self, conn: sqlite3.Connection) -> None:
        self.conn = conn
        migrate(conn)
    def add(self, title: str) -> int:
        with self.conn:
            cur = self.conn.execute("INSERT INTO notes (title) VALUES (?)", (title,))
        return int(cur.lastrowid)
20.
kb/
├── pyproject.toml      [project] name="kb"  [project.scripts] kb = "kb.cli:app"  [build-system] uv_build
├── src/kb/__init__.py  __main__.py  cli.py  ...
└── tests/
uv run kb(入口点,包已安装进 .venv)和 python -m kb(按模块运行,__main__.py)都可以;python src/kb/cli.py 会失败:直接以脚本运行时 sys.path[0]src/kb/import kb.xxx 找不到 kb 包(ModuleNotFoundError)——这正是 src 布局要防止的"只在仓库目录里能跑"。