HOWTO · Python
在 Python 中调用外部程序
本教程演示了如何在 Python 中运行外部程序
本页内容
对于大多数常见的外部命令,使用 subprocess.run()。将可执行文件及其参数以列表形式传递,这样 Python 就不会让 shell 来解析它们。当你需要可读的输出时,设置 capture_output=True 和 text=True,然后在已完成的进程对象上检查 stdout、stderr 和 returncode。下面的示例启动的是 Python 本身,因此不依赖于是否安装了 shell 工具。
运行此示例:
本节中的代码以及显示的标签或输出保持英文。
import subprocess
import sys
result = subprocess.run(
[sys.executable, "-c", "print('Hello from Python')"],
capture_output=True,
text=True,
check=True,
)
print(result.stdout, end="")
print("Return code:", result.returncode)
Hello from Python
Return code: 0
列表使每个参数保持独立,包括参数内部的空格。text=True 使用平台的文本编码将捕获的流解码为字符串;没有它时,stdout 和 stderr 是字节。check=True 会使非零的命令状态引发 subprocess.CalledProcessError。当你想自行处理状态时,不要设置它。捕获的输出与命令的状态是分开的:命令可以写入任一流,但仍以非零的 returncode 结束。
使用 os.system() 运行 shell 命令
os.system() 通过命令处理器(在常见平台上是 shell)运行其字符串,并返回命令处理器的状态。命令的标准输出和标准错误通常仍与父进程相连,因此此调用不会提供捕获的文本。当有意使用 shell 行为时,它是运行简单 shell 命令的旧式选择。如果需要参数列表、捕获的流或结构化的状态处理,请优先使用 subprocess.run()。
由于输入是 shell 语法,不要通过插入不受信任的文本来构建字符串。Shell 元字符可以改变实际运行的内容。确切的命令处理器和状态编码因平台而异,因此不要假设返回值始终是子程序未经修改的退出代码。例如,可移植的概念性调用是 os.system("some-command");请为它将运行的操作系统选择具体的命令和引用规则。
使用 os.popen() 读取命令输出
os.popen() 打开一条通向通过 shell 运行的命令的管道,并返回一个类似文件的对象。读取该流,并在完成后将其关闭;close() 还可以提供命令的状态。此接口是用于简单的基于管道的交互的旧式选项,它不提供通过 subprocess 可用的对 stdout 和 stderr 的分开处理。
例如,其形式为 pipe = os.popen(command),然后是 output = pipe.read() 和 status = pipe.close()。命令字符串由 shell 解释,因此对不受信任的输入要像对 os.system() 一样小心。close() 的返回值不能代替捕获的输出,其状态表示可能取决于平台。对于既需要输出又需要明确返回代码的新代码,请优先使用 subprocess.run()。
使用 subprocess.Popen() 进行更底层的进程控制
subprocess.Popen() 是当程序需要在进程运行时与其交互时使用的类构造器。传递参数列表以直接执行。仅当你打算读取这些流时才设置 stdout=subprocess.PIPE 或 stderr=subprocess.PIPE。调用 communicate() 来收集已配置的管道并等待完成,或者在不需要收集管道输出时调用 wait()。
当子进程大量写入另一个管道时,手动读取一个管道可能会填满操作系统的缓冲区并使进程停滞。communicate() 是将两个流一起收集的常用方法;除非启用了文本模式,否则它返回字节。对于大量或流式输出,应有意识地处理这些流,而不是将所有内容都缓冲在内存中。如果你只需要等待并检查已完成的结果,subprocess.run() 更简单。
使用 subprocess.call() 运行命令并等待
subprocess.call() 运行一个命令,等待它完成,并返回其状态。提供一个列表,例如 subprocess.call(["program", "--version"]),以避免 shell 解析。这个便捷函数默认不捕获输出;子进程的流是继承的。当你需要捕获的流、文本解码或 CompletedProcess 字段时,使用 subprocess.run()。call() 在现有代码只需要同步状态时仍然有用,但新代码通常能从更清晰的 run() 接口中受益。
状态为零通常表示成功,而非零状态表示命令报告了问题。特定非零值的含义属于被调用的程序,状态约定可能因平台而异。如果你需要将非零状态视为错误,subprocess.run(..., check=True) 会引发 CalledProcessError;否则,检查 returncode 并决定你的应用程序应如何处理。
大多数外部命令使用 subprocess.run()
对于常见任务,subprocess.run() 会等待完成并返回一个 CompletedProcess。使用 capture_output=True 时,结果包含 stdout 和 stderr;使用 text=True 时,这些值是字符串。该示例打印标准输出和返回代码,而标准错误仍可在 result.stderr 中获得。如果程序将输出写入标准错误,它不会出现在 result.stdout 中。
优先使用参数列表,并为直接调用程序保持默认的 shell=False。这样可以防止参数中的 shell 元字符被解释为 shell 语法,尽管目标程序本身仍有其输入和选项处理方面的风险。仅当需要管道或重定向等 shell 功能时才使用 shell=True,并且此时应保持 shell 命令固定,或使用适合平台的谨慎引用方式来构建它。参数列表是可移植的默认选择,但可执行文件的名称和命令行选项在不同操作系统之间仍然不同。
当命令失败时,选择如何处理该结果:使用 check=True 以 CalledProcessError 停止,或将其保持为 false 并根据 result.returncode 进行分支处理。可执行文件缺失或无法启动进程会在存在已完成的结果之前引发操作系统错误。不要仅凭输出文本推断成功;应使用状态,并在相关时检查两个流。
结论
大多数外部命令都使用 subprocess.run():它使参数保持独立,等待进程完成,并提供包含状态和任何捕获的流的结果。当你需要在进程运行时进行更底层的交互时,使用 subprocess.Popen()。subprocess.call() 是一个只返回状态的小型同步便捷函数。os.system() 和 os.popen() 是基于 shell 的旧式接口;仅当它们的行为适合任务时才选择它们。保持 shell 输入受信任,只捕获你需要的流,并考虑不同平台之间可执行文件名称和状态约定的差异。