技术 · 实用指南
终端能运行,systemd 却报 ModuleNotFoundError?先证明解释器与 sys.path
重装任何包之前,先记录服务的解释器、前缀、用户、工作目录和模块搜索路径;只修复一个已被证实的边界,再重复同一导入探针。

只在 systemd 下出现的 ModuleNotFoundError,应先视为运行时边界差异。服务可能使用了不同的 Python、虚拟环境、用户、主目录、工作目录或模块搜索路径。
不要先用 sudo pip、chmod、全局 PYTHONPATH 或交互式 activate。应在两个环境运行同一个小型脱敏探针,比较后只做一项可回退修改。
冻结失败服务与成功终端
保存 systemctl cat、关键 unit 属性与完整 journal 回溯。EnvironmentFile 只记录变量名和脱敏路径,不复制秘密。
在成功终端与服务身份下分别记录 id、pwd、Python 路径、pip 归属和同一个诊断探针;比较后移除临时探针。
建立一份导入边界账本
记录 sys.executable 及真实路径、sys.prefix/base_prefix、cwd、uid/gid、HOME、sys.path 和 find_spec 结果。
还要用这个解释器执行 python -m pip show;另一个 pip 显示已安装,不代表服务解释器可以导入。
先分类第一处差异
若解释器或前缀不同,让 ExecStart 直接指向目标虚拟环境 Python;无需先激活。用户不同则把依赖放入服务专用环境。
若只有 cwd 不同,设置绝对 WorkingDirectory 或正确打包项目。若 sys.path 不同,先说明缺少项由哪个文档化输入产生。
修复包边界而非症状
优先使用可复现、由服务用户可读的虚拟环境或已安装应用,并始终使用目标解释器的 python -m pip。
Environment= 只放明确的非秘密配置。窄 PYTHONPATH 可作试验,但不能用 root 运行来掩盖安装归属。
重载、重启并重复探针
必要时 daemon-reload 并重启 unit,然后重复完全相同的账本,确认解释器、前缀、用户、cwd 与模块 spec。
再测试一项真实工作并检查 journal。只有重启后仍从预期环境导入,且没有全局路径黑魔法或扩大权限,才算结束。
用失败分支排除假修复
若同一解释器下 pip show 成功而 find_spec 为空,检查发行包名与导入名是否不同、可选组件或 editable 路径是否失效。
若 systemd 返回 200/CHDIR,先修工作目录;若找不到 ExecStart,先修可执行文件。这些都发生在 Python 导入之前。
