返回专题首页

Python 专题

标准库工具箱(下):datetime、logging、subprocess 与 pathlib

除了容器和函数式工具之外,Python 标准库里还有一批更偏工程实践的成员。时间处理、日志记录、子进程调用、路径操作,这些能力几乎贯穿了所有真实项目。

Python 专题第 21 篇 / 39 篇5 分钟

除了容器和函数式工具之外,Python 标准库里还有一批更偏工程实践的成员。时间处理、日志记录、子进程调用、路径操作,这些能力几乎贯穿了所有真实项目。

这一节我们会把这些“高频但容易踩坑”的模块放在一起梳理,不求一次记住所有 API,而是先建立正确的使用心智,让你知道这些问题到底该交给谁来解决。

datetime:时间处理为什么容易出错?

时间处理之所以麻烦,不是因为 API 难,而是因为时间本身就有很多语义差异:

  • 当前时间还是业务时间?
  • 本地时间还是 UTC?
  • 时间点还是时间段?
  • 字符串格式还是时间对象?

Python 的 datetime 模块解决的是“如何用明确对象表示时间,并进行解析、格式化和运算”。

所以这里最重要的心智,不是背多少方法,而是尽量避免在代码里长期传来传去的都是裸字符串。只要进入计算、比较或存储阶段,通常都应该尽早转成明确的时间对象。

一个最小但实用的例子如下:

from datetime import datetime, timezone

raw_text = "2026-04-10T13:30:00+08:00"
dt = datetime.fromisoformat(raw_text)
utc_dt = dt.astimezone(timezone.utc)

print(dt.isoformat())
print(utc_dt.isoformat())

这段代码真正想说明的,不是某个 API 名字,而是两个习惯:

  • 尽量优先使用可逆、标准化的时间格式;
  • 在进入跨系统传输和存储时,尽量尽早统一到 UTC 视角。

logging:不要再只会 print

print 当然能看结果,但它不是日志系统。真正的日志除了输出内容,还应该携带:

  • 级别;
  • 时间;
  • 来源模块;
  • 必要时的上下文信息。

日志级别与格式化

logging 模块允许你区分不同级别,比如 DEBUGINFOWARNINGERROR

这很重要,因为日志不是越多越好,而是要让不同场景能筛到合适信息。开发调试时你可能想看详细过程,生产环境则更关心异常和关键状态。

业务日志的基本组织

成熟一点的日志使用方式,通常会遵循两个原则:

  • 不直接散落一堆 print
  • 让日志内容能服务于排障,而不是只留下“到了这里”“执行成功”这种模糊句子。

也就是说,日志记录的是“对定位问题有帮助的上下文”,而不是情绪化流水账。

如果你现在还停留在 print("start") 这种阶段,可以先从下面这个最小例子开始:

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s [%(name)s] %(message)s",
)

logger = logging.getLogger(__name__)

logger.info("start syncing articles")
logger.warning("article %s missing summary", 1024)

这比零散的 print 更适合进入脚本、服务和定时任务,因为它至少已经把时间、级别和来源模块带进来了。

subprocess:安全地调用外部命令

Python 经常会被拿来写自动化脚本,而自动化脚本又很常需要调用系统命令、Git、Docker、ffmpeg 之类外部程序。

subprocess 的作用,就是用更安全、结构化的方式完成这件事。

相比直接用老旧方式拼 shell 字符串,subprocess 更适合:

  • 明确参数拆分;
  • 获取返回码;
  • 捕获标准输出和错误输出;
  • 判断命令执行是否成功。

这里最值得强调的一点是:把外部命令当成不可靠边界来对待。

它可能失败、超时、输出异常,所以调用外部命令时要像处理外部接口一样认真。

一个比较稳妥的起手式通常像这样:

import subprocess

result = subprocess.run(
    ["git", "status", "--short"],
    check=True,
    capture_output=True,
    text=True,
    timeout=10,
)

print(result.stdout)

这里建议你优先记住的是这些工程习惯,而不是参数名本身:

  • 参数尽量拆成列表而不是手写一整串 shell;
  • 给超时;
  • 需要结果时显式捕获输出;
  • 能明确要求成功时就用 check=True

pathlib:项目路径定位与目录扫描

虽然上一节已经讲过 pathlib 的基础路径拼接,但放到标准库工具箱下篇时,更值得关注的是它在项目级场景里的作用,比如定位项目根目录、扫描文件、查找配置和批量处理目录树。

相比零散使用 os.pathpathlib 更统一,也更适合和文件 I/O、配置读取、脚本路径定位配合使用。

例如:

from pathlib import Path

project_root = Path(__file__).resolve().parent
config_path = project_root / "config" / "settings.toml"

如果你需要进一步做目录扫描,它也会很顺手:

from pathlib import Path

docs_dir = Path("docs")

for path in docs_dir.rglob("*.md"):
    print(path)

这类写法在脚本、CLI、服务项目里都非常常见。

所以如果你正在建立一套长期使用的工程习惯,把路径处理统一到 pathlib 上,通常会明显减少很多零碎错误;而当项目进入“需要定位根目录、扫描一批文件、批量处理资源”阶段时,它的价值会比简单拼路径更明显。

总结与预告

这一节我们把几类工程里最常见的标准库模块整理成了一套更稳定的使用心智。时间、日志、命令调用和路径操作看起来分散,但它们共同构成了很多脚本和服务的基础能力。

下一节我们会进一步进入工程地带,系统讨论虚拟环境、依赖安装与 pyproject.toml 等依赖管理问题。