Python
命令行、配置与日志
用 argparse、TOML、环境变量和 logging 构建可配置、可诊断的命令行入口。
发布于 2026年7月23日
命令行、配置与日志
命令行入口负责把文本参数转换为应用调用,再把结果转换为输出和退出码。它不应承载数据库细节或业务规则。配置和日志也应在入口完成组装,而不是散落在各模块。
一、学习目标
- 使用
argparse设计子命令 - 返回稳定的退出码
- 用 TOML 和环境变量加载配置
- 区分用户输出与诊断日志
- 避免记录令牌和隐私数据
二、设计子命令
import argparse
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog="study-tasks",
description="管理本地学习任务",
)
subparsers = parser.add_subparsers(dest="command", required=True)
add = subparsers.add_parser("add", help="新增任务")
add.add_argument("title")
listing = subparsers.add_parser("list", help="列出任务")
listing.add_argument("--status", choices=["todo", "done"])
complete = subparsers.add_parser("done", help="完成任务")
complete.add_argument("task_id", type=int)
return parser
效果:
study-tasks --help
study-tasks add "阅读 Python 文档"
study-tasks list --status todo
study-tasks done 1
参数解析只负责语法层校验;标题非空、任务是否存在等属于应用服务。
三、入口函数与退出码
from collections.abc import Sequence
def main(argv: Sequence[str] | None = None) -> int:
args = build_parser().parse_args(argv)
try:
dispatch(args)
except TaskError as exc:
print(f"错误:{exc}")
return 2
return 0
if __name__ == "__main__":
raise SystemExit(main())
常用约定:
0:成功2:参数或可预期的用户输入错误- 其他非零值:应用按文档定义
测试可直接调用 main(["add", "阅读"]),无需修改真实的 sys.argv。
四、TOML 配置
study_tasks.toml:
[study_tasks]
database = "tasks.db"
api_url = "https://api.example.com/tasks"
读取:
import tomllib
from pathlib import Path
def load_toml(path: Path) -> dict[str, object]:
if not path.exists():
return {}
with path.open("rb") as file:
value = tomllib.load(file)
section = value.get("study_tasks", {})
if not isinstance(section, dict):
raise ValueError("[study_tasks] 必须是 TOML 表")
return section
tomllib 只读 TOML。应用通常不需要自动重写用户配置。
五、环境变量与优先级
import os
database = os.getenv("STUDY_TASKS_DB") or str(
config.get("database", "tasks.db")
)
本项目采用:
- 环境变量
- TOML 配置
- 程序默认值
同一项目必须固定优先级并写进文档。密钥只从环境或受保护的秘密系统读取,不写入公开配置和版本库。
六、日志
import logging
logger = logging.getLogger(__name__)
def complete(task_id: int) -> None:
logger.info("completing task id=%s", task_id)
入口统一配置:
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s %(message)s",
)
使用日志参数占位,而不是预先构造 f-string:
logger.debug("loaded %d tasks", len(tasks))
日志级别:
DEBUG:开发诊断细节INFO:正常生命周期事件WARNING:可恢复异常状况ERROR:当前操作失败CRITICAL:服务无法继续
七、用户输出与日志分离
print("已创建 #3 阅读 Python 文档")
logger.info("task created id=%s", task.id)
print 是命令输出协议的一部分,适合管道和用户阅读;日志用于诊断,通常写到标准错误或日志系统。
如果命令提供 --json,标准输出必须只包含 JSON,日志不能混入。
八、敏感信息
禁止记录:
- API Token、密码和 Cookie
- 完整数据库连接串
- 用户隐私字段
- 未经处理的完整请求体
可以记录请求 ID、任务 ID、耗时、状态码和经过白名单处理的字段。
九、常见错误
- 在每个模块重复调用
basicConfig。 - 配置优先级随调用顺序变化。
- 捕获异常后同时打印、记录并再次在上层记录。
- 成功和错误都返回退出码
0。 - 把业务服务写成依赖
argparse.Namespace。
十、练习与自测
- 为 CLI 增加
delete子命令和明确退出码。 - 实现
--verbose,只改变日志级别。 - 测试环境变量覆盖 TOML,而 TOML 覆盖默认值。
自测:
- 参数解析与业务校验为什么应分层?
- 日志和标准输出分别服务谁?
- 配置优先级为何必须固定?
十一、官方资料
上一篇:文件系统、序列化、正则与时间 | 下一篇:SQLite、事务与数据访问