如何使用子进程在Python3中运行外部程序
作者选择了 COVID-19 Relief Fund 作为 Write for DOnations 计划的一部分来接受捐赠。
介绍
Python 3 包含 子进程模块 用于运行外部程序并在 Python 代码中读取它们的输出。
如果您想在 Python 代码中使用计算机上的另一个程序,您可能会发现 subprocess
很有用。 例如,您可能希望从 Python 代码中调用 git 来检索项目中在 git
版本控制中跟踪的文件。 由于您可以在计算机上访问的任何程序都可以由 subprocess
控制,因此此处显示的示例将适用于您可能希望从 Python 代码调用的任何外部程序。
subprocess
包含多个类和函数,但在本教程中,我们将介绍 subprocess
最有用的函数之一:subprocess.run。 我们将回顾它的不同用途和主要关键字参数。
先决条件
为了充分利用本教程,建议您熟悉 Python 3 中的编程。 您可以查看这些教程以获取必要的背景信息:
运行外部程序
您可以使用 subprocess.run
函数从 Python 代码运行外部程序。 不过,首先,您需要将 subprocess
和 sys
模块导入您的程序:
import subprocess import sys result = subprocess.run([sys.executable, "-c", "print('ocean')"])
如果你运行它,你将收到如下输出:
Outputocean
让我们回顾一下这个例子:
- sys.executable 是您的程序最初被调用的 Python 可执行文件的绝对路径。 例如,
sys.executable
可能是类似/usr/local/bin/python
的路径。 subprocess.run
给出了一个字符串列表,其中包含我们尝试运行的命令的组件。 由于我们传递的第一个字符串是sys.executable
,我们正在指示subprocess.run
执行一个新的 Python 程序。-c
组件是一个python
命令行选项,它允许您将字符串传递给整个 Python 程序以执行。 在我们的例子中,我们传递了一个打印字符串ocean
的程序。
您可以将列表中我们传递给 subprocess.run
的每个条目视为由空格分隔。 例如,[sys.executable, "-c", "print('ocean')"]
大致转换为 /usr/local/bin/python -c "print('ocean')"
。 请注意,subprocess
会在尝试在底层操作系统上运行命令组件之前自动引用它们,例如,您可以传递包含空格的文件名。
警告: 切勿将不受信任的输入传递给 subprocess.run
。 由于 subprocess.run
具有在您的计算机上执行任意命令的能力,恶意行为者可以使用它以意想不到的方式操纵您的计算机。
从外部程序捕获输出
现在我们可以使用 subprocess.run
调用外部程序,让我们看看如何捕获该程序的输出。 例如,如果我们想使用 git ls-files
输出当前存储在版本控制下的所有文件,此过程可能很有用。
注意: 本节中的示例需要 Python 3.7 或更高版本。 特别是,在 2018 年 6 月发布的 Python 3.7 中添加了 capture_output
和 text
关键字参数。
让我们添加到前面的示例:
import subprocess import sys result = subprocess.run( [sys.executable, "-c", "print('ocean')"], capture_output=True, text=True ) print("stdout:", result.stdout) print("stderr:", result.stderr)
如果我们运行此代码,我们将收到如下输出:
Outputstdout: ocean stderr:
这个例子与第一节介绍的例子大体相同:我们仍在运行一个子进程来打印 ocean
。 然而,重要的是,我们将 capture_output=True
和 text=True
关键字参数传递给 subprocess.run
。
subprocess.run
返回绑定到 result
的 subprocess.CompletedProcess 对象。 subprocess.CompletedProcess
对象包括有关外部程序退出代码及其输出的详细信息。 capture_output=True
确保 result.stdout
和 result.stderr
填入外部程序的相应输出。 默认情况下,result.stdout
和 result.stderr
绑定为字节,但 text=True
关键字参数指示 Python 将字节解码为字符串。
在输出部分,stdout
是 ocean
(加上 print
隐式添加的尾随换行符),我们没有 stderr
。
让我们尝试一个为 stderr
生成非空值的示例:
import subprocess import sys result = subprocess.run( [sys.executable, "-c", "raise ValueError('oops')"], capture_output=True, text=True ) print("stdout:", result.stdout) print("stderr:", result.stderr)
如果我们运行此代码,我们会收到如下输出:
Outputstdout: stderr: Traceback (most recent call last): File "<string>", line 1, in <module> ValueError: oops
此代码运行一个 Python 子进程,该子进程立即引发 ValueError
。 当我们检查最终的 result
时,我们在 stdout
和 stderr
中的 ValueError
的 Traceback
中什么也看不到。 这是因为默认情况下 Python 会将未处理异常的 Traceback
写入 stderr
。
在错误的退出代码上引发异常
有时,如果我们运行的程序以错误的退出代码退出,则引发异常很有用。 以零代码退出的程序被认为是成功的,但以非零代码退出的程序被认为遇到了错误。 例如,如果我们想在实际上不是 git
存储库的目录中运行 git ls-files
时引发异常,则此模式可能很有用。
如果外部程序返回非零退出代码,我们可以使用 subprocess.run
的 check=True
关键字参数来引发异常:
import subprocess import sys result = subprocess.run([sys.executable, "-c", "raise ValueError('oops')"], check=True)
如果我们运行此代码,我们会收到如下输出:
OutputTraceback (most recent call last): File "<string>", line 1, in <module> ValueError: oops Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/lib/python3.8/subprocess.py", line 512, in run raise CalledProcessError(retcode, process.args, subprocess.CalledProcessError: Command '['/usr/local/bin/python', '-c', "raise ValueError('oops')"]' returned non-zero exit status 1.
此输出显示我们运行了一个引发错误的子进程,该错误在终端的 stderr
中打印。 然后 subprocess.run
在我们的主要 Python 程序中代表我们尽职尽责地提出了一个 subprocess.CalledProcessError
。
或者,subprocess
模块还包括 subprocess.CompletedProcess.check_returncode 方法,我们可以调用它来达到类似的效果:
import subprocess import sys result = subprocess.run([sys.executable, "-c", "raise ValueError('oops')"]) result.check_returncode()
如果我们运行此代码,我们将收到:
OutputTraceback (most recent call last): File "<string>", line 1, in <module> ValueError: oops Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/lib/python3.8/subprocess.py", line 444, in check_returncode raise CalledProcessError(self.returncode, self.args, self.stdout, subprocess.CalledProcessError: Command '['/usr/local/bin/python', '-c', "raise ValueError('oops')"]' returned non-zero exit status 1.
由于我们没有将 check=True
传递给 subprocess.run
,因此我们成功地将 subprocess.CompletedProcess
实例绑定到 result
,即使我们的程序以非零代码退出。 但是,调用 result.check_returncode()
会引发 subprocess.CalledProcessError
,因为它检测到已完成的进程以错误代码退出。
使用超时提前退出程序
subprocess.run
包括 timeout
参数,以允许您在执行时间过长时停止外部程序:
import subprocess import sys result = subprocess.run([sys.executable, "-c", "import time; time.sleep(2)"], timeout=1)
如果我们运行此代码,我们将收到如下输出:
OutputTraceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/lib/python3.8/subprocess.py", line 491, in run stdout, stderr = process.communicate(input, timeout=timeout) File "/usr/local/lib/python3.8/subprocess.py", line 1024, in communicate stdout, stderr = self._communicate(input, endtime, timeout) File "/usr/local/lib/python3.8/subprocess.py", line 1892, in _communicate self.wait(timeout=self._remaining_time(endtime)) File "/usr/local/lib/python3.8/subprocess.py", line 1079, in wait return self._wait(timeout=timeout) File "/usr/local/lib/python3.8/subprocess.py", line 1796, in _wait raise TimeoutExpired(self.args, timeout) subprocess.TimeoutExpired: Command '['/usr/local/bin/python', '-c', 'import time; time.sleep(2)']' timed out after 0.9997982999999522 seconds
我们尝试运行的子进程使用 time.sleep 函数 休眠 2
秒。 但是,我们将 timeout=1
关键字参数传递给 subprocess.run
以在 1
秒后使我们的子进程超时。 这就解释了为什么我们对 subprocess.run
的调用最终会引发 subprocess.TimeoutExpired
异常。
请注意,subprocess.run
的 timeout
关键字参数是近似的。 Python 将尽最大努力在 timeout
秒数后终止子进程,但不一定准确。
将输入传递给程序
有时程序期望输入通过 stdin
传递给它们。
subprocess.run
的 input
关键字参数允许您将数据传递给子进程的 stdin
。 例如:
import subprocess import sys result = subprocess.run( [sys.executable, "-c", "import sys; print(sys.stdin.read())"], input=b"underwater" )
运行此代码后,我们将收到如下输出:
Outputunderwater
在这种情况下,我们将字节 underwater
传递给 input
。 我们的目标子进程使用 sys.stdin 读取传入的 stdin
(underwater
) 并将其打印到我们的输出中。
如果要将多个 subprocess.run
调用链接在一起,将一个程序的输出作为输入传递给另一个程序,则 input
关键字参数可能很有用。
结论
subprocess
模块是 Python 标准库的一个强大部分,可让您运行外部程序并轻松检查其输出。 在本教程中,您学习了使用 subprocess.run
来控制外部程序、将输入传递给它们、解析它们的输出并检查它们的返回码。
subprocess
模块公开了我们在本教程中没有介绍的其他类和实用程序。 现在您有了基线,您可以使用 子流程模块的文档 了解更多关于其他可用类和实用程序的信息。