← 返回笔记

Python 小工具:把学习记录读成数据

从一行文本出发,保留时间、主题与分钟数,并让格式错误带着上下文出现。

本文为站点内容示例,记录格式与数据均为演示,不代表个人实际学习统计。

先约定一行的含义

一个小脚本不必一开始就接入数据库。先约定每行包含时间、主题和分钟数,中间用竖线分隔。例如 2026-10-20T09:00:00+00:00 | C 位运算 | 25。时间保留时区,主题不能为空,分钟数必须是正整数。主题本身不使用竖线,避免引入不必要的转义规则。

下面的函数只负责解析一行。文件读取、数据汇总和结果展示留给调用方处理,这样可以直接传入字符串测试。时间解析使用标准库的 datetime.fromisoformat,同时显式检查时区信息。

import logging
from datetime import datetime

logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s [DEBUG] %(funcName)s: %(message)s",
)
logger = logging.getLogger(__name__)


def parse_study_line(line: str, line_no: int):
    try:
        stamp, topic, raw_minutes = (
            part.strip() for part in line.strip().split("|")
        )
        started_at = datetime.fromisoformat(stamp)
        minutes = int(raw_minutes)
        if started_at.tzinfo is None:
            raise ValueError("时间需要包含时区偏移")
        if not topic or minutes <= 0:
            raise ValueError("主题不能为空,分钟数必须大于零")
        return started_at, topic, minutes
    except ValueError as exc:
        logger.debug(
            "解析失败 line_no=%d raw=%r", line_no, line,
            exc_info=True,
        )
        raise ValueError(f"第 {line_no} 行学习记录无效:{exc}") from exc

错误要能够回到原始输入

只输出“解析失败”,很难判断是哪一行出了问题。这里记录了日志时间、函数名、行号、原始文本和异常堆栈;重新抛出的异常也保留了原因。调用方可以选择停止导入,或者把失败行保存为待修复清单,不能悄悄把错误分钟数当作零。

演示日志使用本机时间记录程序运行时刻,学习记录中的时间则由输入自带时区。两者含义不同,汇总前应明确采用哪一种日期边界。若记录里有私人内容,实际使用时可以只输出行号和必要字段,避免整行日志扩散。

给下一次修改留一个起点

至少准备四类输入:正常记录、缺失字段、错误日期和负数分钟。空主题与没有时区的时间也值得单独覆盖。先看这些输入是否产生预期结果,再增加按主题汇总的功能。小工具的价值,在于输入约定清楚、错误可定位,也能在隔一周之后重新跑起来。

读取文件时,另外处理路径不存在和编码错误,并在报错信息中包含文件名。不要把文件错误塞进单行解析函数;让每一层只解释自己掌握的上下文,测试也就可以分别覆盖“文件打不开”和“这一行格式不对”两种情况。