作者选择了 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/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_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)来了解更多关于其他可用的类和实用程序。