作者选择了 COVID-19 救援基金作为 Write for Donations计划的一部分接受捐款。
介绍
Python 3 包含 subprocess 模块用于运行外部程序并在 Python 代码中读取其输出。
例如,您可能想从您的 Python 代码中调用 git以获取您项目中的文件,这些文件在git版本控制中被跟踪。
「subprocess」包括幾個類別和函數,但在本教程中,我們將涵蓋「subprocess」最有用的函數之一: `subprocess.run'。
前提条件
要充分利用本教程,建议您对Python编程有熟悉,您可以查看这些教程以获取必要的背景信息:
【如何在Python中编码】( )
运行外部程序
您可以使用subprocess.run函数从 Python 代码中运行外部程序,但首先,您需要将subprocess和sys模块导入到您的程序中:
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/pythonsubprocess.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_output 和 text 关键字参数。
让我们添加到我们以前的例子:
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代替将字节解码成字节。
在输出部分中,stdout是ocean(加上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中看到我们的ValueError的Traceback。
对一个坏的退出代码提出例外
有时,如果我们运行的程序有一个坏的输出代码,那么提出例外是有用的。 以零代码输出的程序被认为是成功的,但与非零代码输出的程序被认为遇到了错误。
我们可以使用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)来了解更多关于其他可用的类和实用程序。