Python 调外部命令时,怎样区分启动失败、退出失败和超时
一个定时任务用 Python 调用外部转换工具。早晨只看到一条“执行失败”的日志,却不知道机器上缺少程序、程序已经报错退出,还是它一直在等待。如果把这三种情况都交给同一套重试逻辑,缺少的程序不会自己出现,已经写出一半的文件也可能被重复处理。
subprocess.run() 能提供区分这些情况所需的信息。下面的例子用当前 Python 解释器模拟正常退出、报错退出和运行超时,再用临时目录里一个不存在的路径模拟启动失败。复制后可以直接运行,无需安装其他工具。
import subprocess
import sys
from pathlib import Path
from tempfile import TemporaryDirectory
def as_text(value):
if isinstance(value, bytes):
return value.decode("utf-8", errors="replace").strip()
return (value or "").strip()
def run_job(label, command):
try:
result = subprocess.run(
command,
check=True,
capture_output=True,
encoding="utf-8",
errors="replace",
timeout=2,
)
except subprocess.TimeoutExpired as error:
print(label, "超时", error.timeout, as_text(error.stdout))
except subprocess.CalledProcessError as error:
print(label, "退出失败", error.returncode, as_text(error.stderr))
except FileNotFoundError:
print(label, "启动失败,找不到可执行文件")
else:
print(label, "成功", as_text(result.stdout))
python = [sys.executable, "-u", "-c"]
run_job("正常", python + ["print('ready')"])
run_job("非零退出", python + [
"import sys; print('invalid config', file=sys.stderr); sys.exit(2)"
])
run_job("慢任务", python + [
"import time; print('started'); time.sleep(8)"
])
with TemporaryDirectory() as folder:
run_job("缺少程序", [str(Path(folder) / "missing-program.exe")])
正常 成功 ready
非零退出 退出失败 2 invalid config
慢任务 超时 2 started
缺少程序 启动失败,找不到可执行文件
退出失败说明子进程已经启动并返回了非零退出码。check=True 会把这个结果变成 CalledProcessError,从中可以取到退出码和捕获的标准错误。例子中的 invalid config 直接指向配置问题。如果改用 check=False,就要自己检查 result.returncode。
超时说明子进程在限定时间内没有结束。run() 会终止并等待它直接启动的子进程,再抛出 TimeoutExpired。该异常中的部分输出可能是字节串,即使正常返回时使用了文本模式,所以例子用了 as_text() 统一处理。-u 让示例子进程及时刷出 started,避免缓冲造成误判。实际工具如果还会派生子进程,清理进程树需要单独处理。
启动失败发生在程序运行之前,常见原因是可执行文件不存在,也可能遇到权限错误。用参数列表传入程序和参数,路径有空格时也无需手动拼接引号。把错误类型、退出码或超时上限写入日志,输出量较大时写到日志文件。决定是否重试前,先核对命令已经产生的文件或外部操作,尤其是会覆盖文件或提交请求的任务。
参考资料为 Python 标准库《subprocess — Subprocess management》中的 run() 和异常章节。
- 点赞
- 收藏
- 关注作者
评论(0)