Python
文件系统、序列化、正则与时间
通过 pathlib、JSON、正则和带时区时间安全处理常见本地数据边界。
发布于 2026年7月23日
文件系统、序列化、正则与时间
文件、JSON、路径和时间都位于系统边界。边界代码必须明确编码、格式、时区、错误策略和资源生命周期,不能依赖当前机器的偶然默认值。
一、学习目标
- 使用
pathlib处理跨平台路径 - 以 UTF-8 安全读写文本
- 在 JSON 与领域对象之间显式转换
- 适度使用正则表达式
- 使用带时区的日期时间
- 为任务实现导入与导出
二、pathlib
from pathlib import Path
data_dir = Path.home() / ".study_tasks"
data_dir.mkdir(parents=True, exist_ok=True)
tasks_file = data_dir / "tasks.json"
不要手工用 / 或 \ 拼接路径。Path 能表达存在检查、目录遍历、后缀替换和文件操作。
for path in sorted(data_dir.glob("*.json")):
print(path.name, path.stat().st_size)
用户输入的路径可能指向符号链接、目录或不存在的对象,操作前要根据业务需要检查。
三、文本编码
content = tasks_file.read_text(encoding="utf-8")
tasks_file.write_text(content, encoding="utf-8")
始终为持久格式指定编码。平台默认编码会随系统语言和配置变化。
处理大文件时逐行读取:
with tasks_file.open(encoding="utf-8") as file:
for line in file:
process(line)
四、JSON 序列化
import json
from dataclasses import asdict
payload = [asdict(task) for task in tasks]
text = json.dumps(payload, ensure_ascii=False, indent=2, default=str)
tasks_file.write_text(text, encoding="utf-8")
default=str 适合演示,但正式格式应显式决定日期时间和枚举表示:
def task_to_dict(task: Task) -> dict[str, object]:
return {
"id": task.id,
"title": task.title,
"status": task.status.value,
"created_at": task.created_at.isoformat(),
"updated_at": task.updated_at.isoformat(),
}
反序列化:
from datetime import datetime
def task_from_dict(value: dict[str, object]) -> Task:
return Task(
id=int(value["id"]),
title=str(value["title"]),
status=TaskStatus(str(value["status"])),
created_at=datetime.fromisoformat(str(value["created_at"])),
updated_at=datetime.fromisoformat(str(value["updated_at"])),
)
外部输入还需要更严格的键、类型和范围检查。
五、JSON Lines
大量记录适合每行一个 JSON 对象:
with Path("tasks.jsonl").open("w", encoding="utf-8") as file:
for task in tasks:
json.dump(task_to_dict(task), file, ensure_ascii=False)
file.write("\n")
优点是可以流式处理,某一行损坏时也能报告准确行号。缺点是文件整体不再是一个标准 JSON 数组。
六、正则表达式的边界
提取 #123 形式的任务引用:
import re
TASK_REFERENCE = re.compile(r"(?<!\w)#(?P<id>[1-9]\d*)\b")
for match in TASK_REFERENCE.finditer("完成 #12,阻塞 #30"):
print(int(match.group("id")))
使用原始字符串 r"..." 减少反斜杠转义。
简单前缀、分隔符和成员判断优先使用字符串方法:
if title.startswith("[urgent]"):
...
不要用正则解析完整 JSON、HTML 或 Python 语法;应使用相应解析器。
七、日期时间和时区
from datetime import UTC, datetime, timedelta
now = datetime.now(UTC)
tomorrow = now + timedelta(days=1)
serialized = now.isoformat()
restored = datetime.fromisoformat(serialized)
持久化和跨系统交换通常使用 UTC 的带时区时间;展示时再转换为用户时区。
from zoneinfo import ZoneInfo
shanghai = restored.astimezone(ZoneInfo("Asia/Shanghai"))
print(shanghai.isoformat())
不要混合比较带时区和不带时区的 datetime。
八、安全导入流程
导入任务时分三个阶段:
- 读取并解析外部格式。
- 验证全部记录,转换为领域对象。
- 验证成功后一次性写入。
不要边解析边覆盖正式数据,否则中间一条错误会留下部分导入结果。
九、常见错误
- 未指定编码,导致换机器后乱码。
- 用字符串拼路径,产生平台分隔符问题。
- 把
pickle用于不可信输入;它可能执行任意代码。 - 使用本地无时区时间跨系统传输。
- 读取完整大文件后才逐行处理。
十、练习与自测
- 实现 JSON Lines 导出和带行号的导入错误。
- 将 UTC 时间显示为 Asia/Shanghai。
- 使用临时文件与替换实现原子 JSON 导出。
自测:
- 为什么外部文本应显式指定编码?
- JSON 解析成功是否代表业务数据有效?
- 什么时候 JSON Lines 比 JSON 数组更合适?
十一、官方资料
上一篇:类型注解、dataclass 与结构化数据 | 下一篇:命令行、配置与日志