如何在 Python 3 中使用子进程运行外部程序

作者选择了 COVID-19 救援基金作为 Write for Donations计划的一部分接受捐款。

介绍

Python 3 包含 subprocess 模块用于运行外部程序并在 Python 代码中读取其输出。

例如,您可能想从您的 Python 代码中调用 git以获取您项目中的文件,这些文件在git版本控制中被跟踪。

「subprocess」包括幾個類別和函數,但在本教程中,我們將涵蓋「subprocess」最有用的函數之一: `subprocess.run'

前提条件

要充分利用本教程,建议您对Python编程有熟悉,您可以查看这些教程以获取必要的背景信息:

【如何在Python中编码】( )

运行外部程序

您可以使用subprocess.run函数从 Python 代码中运行外部程序,但首先,您需要将subprocesssys模块导入到您的程序中:

1import subprocess
2import sys
3
4result = subprocess.run([sys.executable, "-c", "print('ocean')"])

如果您运行此功能,您将收到如下输出:

1[secondary_label Output]
2ocean

让我们来回顾这个例子:

  • sys.executable是您的程序最初被召唤的Python可执行的绝对路径,例如, sys.executable可能是一个路径,如 /usr/local/bin/python
  • subprocess.run是由我们试图运行命令的组件组成的字符串列表。由于我们通过的第一个字符串是 sys.executable,我们正在指示 subprocess.run 执行一个新的Python程序
  • c组件是 python命令行选项,允许您通过一个字符串与整个Python程序进行执行。在我们的情况下,我们通过一个打印字符串的程序

例如,‘[sys.executable, -c, print('ocean')]’大约翻译为‘/usr/local/bin/python -cprint '('ocean')’。

<$>[警告] 警告: 永远不要将不受信任的输入传输到 subprocess.run.由于 subprocess.run在您的计算机上具有执行任意命令的能力,恶意行为者可以使用它以意想不到的方式操纵您的计算机。

从外部程序捕获输出

现在我们可以使用subprocess.run调用外部程序,让我们看看我们如何从该程序中捕获输出,例如,如果我们想要使用git ls-files来输出当前在版本控制下存储的所有文件,这个过程可能是有用的。

<$>[注] **注:**本节所示的示例需要 Python 3.7 或更高版本,特别是在 Python 3.7 发布于 2018 年 6 月时添加了 capture_outputtext 关键字参数。

让我们添加到我们以前的例子:

1import subprocess
2import sys
3
4result = subprocess.run(
5    [sys.executable, "-c", "print('ocean')"], capture_output=True, text=True
6)
7print("stdout:", result.stdout)
8print("stderr:", result.stderr)

如果我们运行此代码,我们将收到如下的输出:

1[secondary_label Output]
2stdout: ocean
3
4stderr:

这个例子大多与第一节所引入的例子相同:我们仍在运行一个子进程来打印海洋

「subprocess.CompletedProcess」返回一个与「结果」相关的对象。「subprocess.CompletedProcess」对象包含外部程序的输出代码及其输出的详细信息。「capture_output=True」确保「result.stdout」和「result.stderr」被填充到来自外部程序的相应输出中。默认情况下,「result.stdout」和「result.stderr」被绑定为字节,但「text=True」关键字参数指示Python代替将字节解码成字节。

在输出部分中,stdoutocean(加上print暗示添加的后续新线),我们没有stderr

让我们尝试一个产生stderr的非空值的例子:

1import subprocess
2import sys
3
4result = subprocess.run(
5    [sys.executable, "-c", "raise ValueError('oops')"], capture_output=True, text=True
6)
7print("stdout:", result.stdout)
8print("stderr:", result.stderr)

如果我们运行此代码,我们会收到如下的输出:

1[secondary_label Output]
2stdout:
3stderr: Traceback (most recent call last):
4  File "<string>", line 1, in <module>
5ValueError: oops

这个代码运行一个Python子进程,即时产生一个ValueError。当我们检查最终的结果,我们在stdout中看不到任何东西,在stderr中看到我们的ValueErrorTraceback

对一个坏的退出代码提出例外

有时,如果我们运行的程序有一个坏的输出代码,那么提出例外是有用的。 以零代码输出的程序被认为是成功的,但与非零代码输出的程序被认为遇到了错误。

我们可以使用check=True关键字参数为subprocess.run,以便在外部程序返回非零输出代码时引发例外:

1import subprocess
2import sys
3
4result = subprocess.run([sys.executable, "-c", "raise ValueError('oops')"], check=True)

如果我们运行此代码,我们会收到如下的输出:

1[secondary_label Output]
2Traceback (most recent call last):
3  File "<string>", line 1, in <module>
4ValueError: oops
5Traceback (most recent call last):
6  File "<stdin>", line 1, in <module>
7  File "/usr/local/lib/python3.8/subprocess.py", line 512, in run
8    raise CalledProcessError(retcode, process.args,
9subprocess.CalledProcessError: Command '['/usr/local/bin/python', '-c', "raise ValueError('oops')"]' returned non-zero exit status 1.

这个输出显示,我们运行了一个子进程,引发了一个错误,在我们的终端中打印为stderr

另一种方式是,子进程模块还包含了 subprocess.CompletedProcess.check_returncode的方法,我们可以用来产生类似的效果:

1import subprocess
2import sys
3
4result = subprocess.run([sys.executable, "-c", "raise ValueError('oops')"])
5result.check_returncode()

如果我们运行此代码,我们将收到:

1[secondary_label Output]
2Traceback (most recent call last):
3  File "<string>", line 1, in <module>
4ValueError: oops
5Traceback (most recent call last):
6  File "<stdin>", line 1, in <module>
7  File "/usr/local/lib/python3.8/subprocess.py", line 444, in check_returncode
8    raise CalledProcessError(self.returncode, self.args, self.stdout,
9subprocess.CalledProcessError: Command '['/usr/local/bin/python', '-c', "raise ValueError('oops')"]' returned non-zero exit status 1.

由于我们没有将check=True传输到subprocess.run,我们成功将subprocess.CompletedProcess实例绑定到result,即使我们的程序用非零代码退出。

使用时限提早退出程序

「subprocess.run」包括「timeout」参数,允许您停止外部程序,如果它需要太长时间来执行:

1import subprocess
2import sys
3
4result = subprocess.run([sys.executable, "-c", "import time; time.sleep(2)"], timeout=1)

如果我们运行此代码,我们将收到如下的输出:

 1[secondary_label Output]
 2Traceback (most recent call last):
 3  File "<stdin>", line 1, in <module>
 4  File "/usr/local/lib/python3.8/subprocess.py", line 491, in run
 5    stdout, stderr = process.communicate(input, timeout=timeout)
 6  File "/usr/local/lib/python3.8/subprocess.py", line 1024, in communicate
 7    stdout, stderr = self._communicate(input, endtime, timeout)
 8  File "/usr/local/lib/python3.8/subprocess.py", line 1892, in _communicate
 9    self.wait(timeout=self._remaining_time(endtime))
10  File "/usr/local/lib/python3.8/subprocess.py", line 1079, in wait
11    return self._wait(timeout=timeout)
12  File "/usr/local/lib/python3.8/subprocess.py", line 1796, in _wait
13    raise TimeoutExpired(self.args, timeout)
14subprocess.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 的例外。

请注意,timeout关键字参数为subprocess.run是近似的,Python将在timeout秒数后尽最大努力杀死子过程,但它不一定是准确的。

输入到程序

有时程序希望输入通过stdin传递给它们。

输入subprocess.run的关键字参数允许您将数据传输到子过程的stdin

1import subprocess
2import sys
3
4result = subprocess.run(
5    [sys.executable, "-c", "import sys; print(sys.stdin.read())"], input=b"underwater"
6)

我们将在运行此代码后收到如下输出:

1[secondary_label Output]
2underwater

在这种情况下,我们将字节水下传输到输入中,我们的目标子进程使用了sys.stdin(https://docs.python.org/3/library/sys.html#sys.stdin)来读取stdin(水下)的传输,并在我们的输出中打印出来。

输入关键字参数可能有用,如果你想链接多个subprocess.run呼叫,将一个程序的输出作为输入传递给另一个程序。

结论

子过程模块是Python标准库的强大部分,允许您运行外部程序并轻松检查其输出. 在本教程中,您已经学会了如何使用子过程来控制外部程序,传输输入,分析其输出,并检查其返回代码。

子进程模块揭示了我们在本教程中未涵盖的其他类和实用程序. 现在你有一个基线,你可以使用子进程模块的文档(https://docs.python.org/3/library/subprocess.html)来了解更多关于其他可用的类和实用程序。

Published At
Categories with 技术
Tagged with
comments powered by Disqus