第 2 周周测答案(默认折叠)¶
闭卷做完、自己判完再看
点击展开
第 2 周周测答案¶
所有输出已在 Python 3.14.4 + Pydantic 2.13 上实际运行核对。
一、预测输出¶
3 int与[]—— lax 模式把"3"转成int;Pydantic 会为每个实例拷贝默认值,a.tags与b.tags不是同一个列表(和 dataclass 的可变默认参数相反)。2 [('name',), ('qty',)]—— 两个字段各一条错误;extra=1默认被忽略。要让它报错:model_config = ConfigDict(extra="forbid")。['name', 'qty'] 0—— 有默认值的tags不在required里;gt=0映射成 JSON Schema 的exclusiveMinimum: 0。{'name': 'a', 'qty': 2, 'tags': []}。含datetime时:model_dump()保留datetime对象(json.dumps会失败),mode="json"转成 ISO 字符串。2026 {'y': '2026', 'm': '09'} ['2026-09', '2026-10']——search只返回第一个匹配;findall返回全部。X X2b None——.*贪婪吃到最后一个b;.*?懒惰到第一个b;re.match只从字符串开头匹配,"ab"开头不是b。kb.service root 0 30—— 点号分层:父 logger 是 root;自身 level 0(NOTSET)表示"继承";有效级别继承 root 的默认 WARNING(30)。1——with conn:在块内抛异常时回滚,第二条插入被撤销;第一条已提交。- 第一行
{"\u540d": "\u503c"},第二行{"名": "值"}—— 默认ensure_ascii=True把非 ASCII 转义。 [1, 2] [3, 4]——TypeAdapter不用建模型也能校验/转换任意类型,validate_json直接吃 JSON 文本。
二、找 bug¶
- SQL 字符串拼接 → 注入 + 引号崩溃(标题里有
'就报错)。改为参数化:execute("... WHERE title LIKE ?", (f"%{pattern}%",))。 - 库模块顶部
basicConfig抢走了根 logger 的配置权:谁 import 了这个模块,全局日志就被改成 DEBUG。库里只写logger = logging.getLogger(__name__),配置只在程序入口做一次。 - 密钥写进代码默认值 → 进入 Git 历史与网站/包;
.env被提交更是直接泄露。默认值应为str(无默认,强制配置)或从环境读取;.env进.gitignore,仓库只放.env.example;已提交的密钥要作废重发。 - ① 没写
encoding="utf-8":中文 Windows 用 GBK 写,别处读会乱码/报错;② 中文变\uXXXX(缺ensure_ascii=False);③ 非原子:写到一半崩溃留下半个损坏文件。正确:写同目录.tmp→os.replace(tmp, path)。 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
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 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
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)
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 布局要防止的"只在仓库目录里能跑"。