Python 中的子进程是什么? [5 用法示例]

已发表: 2021-05-23

子流程让您可以在全新的层面上与操作系统进行交互。

我们的计算机一直在运行子进程。 事实上,仅仅通过阅读本文,您就可以运行许多进程,例如网络管理器或 Internet 浏览器本身。

很酷的一点是,我们在计算机上执行的任何操作都涉及调用子进程。 即使我们在 python 中编写一个简单的“hello world”脚本,这仍然是正确的。

即使您已经学习了一段时间的编程,子流程的概念也可能看起来很模糊。 本文将深入介绍子进程的主要概念,以及如何使用Python子进程标准库。

在本教程结束时,您将:

  • 理解子进程的概念
  • 已经学习了Python子进程库的基础知识
  • 用有用的例子练习你的 Python 技能

让我们进入它

子进程的概念

从广义上讲,子进程是由另一个进程创建的计算机进程。

我们可以将子进程视为一棵树,其中每个父进程都有子进程在其后面运行。 我知道这可能很令人困惑,但让我们用一个简单的图形来看看它。

我们可以通过多种方式将计算机上运行的进程可视化。 例如,在 UNIX(Linux 和 MAC)中,我们有 htop,它是一个交互式进程查看器。

Htop进程查看器

树模式是查看正在运行的子进程的最有用的工具。 我们可以用F5激活它。

如果我们仔细查看命令部分,我们会注意到在我们的计算机上运行的进程的结构。

htop进程结构
这一切都从/sbin/init开始,它是启动我们计算机上每个进程的命令。 从那一点,我们可以看到其他进程的开始,比如xfce4-screenshoterxfce4-terminal (这有助于更多的子进程)

看看 Windows,我们有神话般的任务管理器,它在杀死我们机器上的那些崩溃程序时很有用。

Windows 任务管理器

现在我们有了一个清晰的概念。 让我们看看如何在 Python 中实现子流程。

Python 中的子进程

Python 中的子进程是 Python 脚本委托给操作系统 (OS) 的任务。

子进程库允许我们直接从 Python 执行和管理子进程。 这涉及使用标准输入stdin 、标准输出stdout和返回代码。

我们不必用 PIP 安装它,因为它是 Python 标准库的一部分。

因此,我们可以通过导入模块开始在 python 中使用子进程。

 import subprocess # Using the module ....

注意:要继续阅读本文,您应该拥有 Python 3.5 +

要检查您当前拥有的 python 版本,只需运行。

 ❯ python --version Python 3.9.5 # My result

如果您获得的 Python 版本是 2.x,您可以使用以下命令

python3 --version

继续该主题,子进程库背后的主要思想是能够通过直接从 Python 解释器执行我们想要的任何命令来与操作系统交互。

这意味着我们可以做任何我们想做的事情,只要我们的操作系统允许我们(并且只要您不删除您的根文件系统)。

让我们通过创建一个列出当前目录的文件的简单脚本来看看如何使用它。

第一个子流程应用

首先,让我们创建一个文件list_dir.py 。 这将是我们要试验列出文件的文件。

 touch list_dir.py

现在让我们打开该文件并使用以下代码。

 import subprocess subprocess.run('ls')

首先,我们导入 subprocess 模块,然后使用运行的函数run ,我们将命令作为参数传递。

这个函数是在 Python 3.5 中引入的,作为 subprocess.Popen 的友好快捷方式。 subprocess.run 函数允许我们运行命令并等待它完成,与 Popen 相比,我们可以选择稍后调用通信。

谈到代码输出, ls是一个 UNIX 命令,它列出您所在目录的文件。因此,如果您运行此命令,您将获得当前目录中存在的文件列表。

 ❯ python list_dir.py example.py LICENSE list_dir.py README.md

注意:注意,如果您使用的是 Windows,则需要使用不同的命令。 例如,您可以使用“dir”代替“ls

这可能看起来太简单了,你是对的。 您想全面了解 shell 为您带来的所有功能。 因此,让我们学习如何使用 subprocess 将参数传递给 shell。

例如,要列出隐藏文件(以点开头的文件),并列出文件的所有元数据,我们编写以下代码。

 import subprocess # subprocess.run('ls') # Simple command subprocess.run('ls -la', shell=True)

我们将此命令作为字符串运行并使用参数shell 。 这意味着我们在子进程执行开始时调用一个 shell,并且命令参数由 shell 直接解释。

但是,使用shell=True有很多缺点,最糟糕的是可能存在安全漏洞。 您可以在官方文档中阅读有关它们的信息。

将命令传递给 run 函数的最佳方法是使用一个列表,其中lst[0]是要调用的命令(在本例中为 ls),而lst[n]是该命令的参数。

如果我们这样做,我们的代码将如下所示。

 import subprocess # subprocess.run('ls') # Simple command # subprocess.run('ls -la', shell=True) # Dangerous command subprocess.run(['ls', '-la'])

如果我们想将子进程的标准输出存储在一个变量中,我们可以通过将参数 capture_output 设置为 true 来实现。

 list_of_files = subprocess.run(['ls', '-la'], capture_output=True) print(list_of_files.stdout) ❯ python list_dir.py b'total 36\ndrwxr-xr-x 3 daniel daniel 4096 may 20 21:08 .\ndrwx------ 30 daniel daniel 4096 may 20 18:03 ..\n-rw-r--r-- 1 daniel daniel 55 may 20 20:18 example.py\ndrwxr-xr-x 8 daniel daniel 4096 may 20 17:31 .git\n-rw-r--r-- 1 daniel daniel 2160 may 17 22:23 .gitignore\n-rw-r--r-- 1 daniel daniel 271 may 20 19:53 internet_checker.py\n-rw-r--r-- 1 daniel daniel 1076 may 17 22:23 LICENSE\n-rw-r--r-- 1 daniel daniel 216 may 20 22:12 list_dir.py\n-rw-r--r-- 1 daniel daniel 22 may 17 22:23 README.md\n'

要访问进程的输出,我们使用实例属性stdout

在这种情况下,我们希望将输出存储为字符串,而不是字节,我们可以通过将 text 参数设置为 true 来实现。

 list_of_files = subprocess.run(['ls', '-la'], capture_output=True, text=True) print(list_of_files.stdout) ❯ python list_dir.py total 36 drwxr-xr-x 3 daniel daniel 4096 may 20 21:08 . drwx------ 30 daniel daniel 4096 may 20 18:03 .. -rw-r--r-- 1 daniel daniel 55 may 20 20:18 example.py drwxr-xr-x 8 daniel daniel 4096 may 20 17:31 .git -rw-r--r-- 1 daniel daniel 2160 may 17 22:23 .gitignore -rw-r--r-- 1 daniel daniel 271 may 20 19:53 internet_checker.py -rw-r--r-- 1 daniel daniel 1076 may 17 22:23 LICENSE -rw-r--r-- 1 daniel daniel 227 may 20 22:14 list_dir.py -rw-r--r-- 1 daniel daniel 22 may 17 22:23 README.md

完美,现在我们了解了子流程库的基础知识,是时候继续介绍一些使用示例了。

Python中子进程的使用示例

在本节中,我们将回顾 subprocess 库的一些实际用途。 你可以在这个 Github 存储库中查看所有这些。

程序检查器

该库的主要用途之一是能够进行简单的操作系统操作。

例如,一个检查程序是否安装的简单脚本。 在 Linux 中,我们可以使用which命令执行此操作。

 '''Program checker with subprocess''' import subprocess program = 'git' process = subprocess. run(['which', program], capture_output=True, text=True) if process.returncode == 0: print(f'The program "{program}" is installed') print(f'The location of the binary is: {process.stdout}') else: print(f'Sorry the {program} is not installed') print(process.stderr)

注意:在 UNIX 中,当命令成功时,其状态码为 0。否则,在执行过程中出现问题

由于我们没有使用shell=True参数,我们可以安全地获取用户输入。 此外,我们可以检查输入是否是具有正则表达式模式的有效程序。

 import subprocess import re programs = input('Separe the programs with a space: ').split() secure_pattern = '[\w\d]' for program in programs: if not re.match(secure_pattern, program): print("Sorry we can't check that program") continue process = subprocess. run( ['which', program], capture_output=True, text=True) if process.returncode == 0: print(f'The program "{program}" is installed') print(f'The location of the binary is: {process.stdout}') else: print(f'Sorry the {program} is not installed') print(process.stderr) print('\n')

在这种情况下,我们从用户那里获取程序并使用正则表达式来证明程序字符串仅包含字母和数字。 我们使用 for 循环检查每个程序是否存在。

Python 中的简单 Grep

您的朋友Tom在一个文本文件和另一个大文件中有一个模式列表,他想在其中获取每个模式的匹配数。 他会花几个小时为每个模式运行 grep 命令。

幸运的是,你知道如何用 Python 解决这个问题,你会在几秒钟内帮助他完成这个任务。

 import subprocess patterns_file = 'patterns.txt' readfile = 'romeo-full.txt' with open(patterns_file, 'r') as f: for pattern in f: pattern = pattern.strip() process = subprocess.run( ['grep', '-c', f'{pattern}', readfile], capture_output=True, text=True) if int(process.stdout) == 0: print( f'The pattern "{pattern}" did not match any line of {readfile}') continue print(f'The pattern "{pattern}" matched {process.stdout.strip()} times')

看看这个文件,我们定义了两个变量,它们是我们想要使用的文件名。 然后我们打开包含所有模式的文件并迭代它们。 接下来,我们调用一个子进程,它运行带有“-c”标志(表示计数)的 grep 命令,并使用条件确定匹配的输出。

如果你运行这个文件(记住你可以从 Github repo 下载文本文件)

使用子进程设置 virtualenv

使用 Python 可以做的最酷的事情之一就是流程自动化。 这种脚本每周可以为您节省数小时的时间。

例如,我们将创建一个安装脚本,为我们创建一个虚拟环境,并尝试在当前目录中找到一个requirements.txt文件来安装所有依赖项。

 import subprocess from pathlib import Path VENV_NAME = '.venv' REQUIREMENTS = 'requirements.txt' process1 = subprocess.run(['which', 'python3'], capture_output=True, text=True) if process1.returncode != 0: raise OSError('Sorry python3 is not installed') python_bin = process1.stdout.strip() print(f'Python found in: {python_bin}') process2 = subprocess.run('echo "$SHELL"', shell=True, capture_output=True, text=True) shell_bin = process2.stdout.split('/')[-1] create_venv = subprocess.run([python_bin, '-m', 'venv', VENV_NAME], check=True) if create_venv.returncode == 0: print(f'Your venv {VENV_NAME} has been created') pip_bin = f'{VENV_NAME}/bin/pip3' if Path(REQUIREMENTS).exists(): print(f'Requirements file "{REQUIREMENTS}" found') print('Installing requirements') subprocess.run([pip_bin, 'install', '-r', REQUIREMENTS]) print('Process completed! Now activate your environment with "source .venv/bin/activate"') else: print("No requirements specified ...")

在这种情况下,我们使用多个进程并在我们的 python 脚本中解析我们需要的数据。 我们还使用了 pathlib 库,它允许我们判断requirements.txt文件是否存在。

如果您运行 python 文件,您将获得一些关于操作系统正在发生的事情的有用消息。

 ❯ python setup.py Python found in: /usr/bin/python3 Your venv .venv has been created Requirements file "requirements.txt" found Installing requirements Collecting asgiref==3.3.4 ....... Process completed! Now activate your environment with "source .venv/bin/activate"

请注意,我们从安装过程中获得输出,因为我们没有将标准输出重定向到变量。

运行另一种编程语言

我们可以使用 python 运行其他编程语言并从这些文件中获取输出。 这是可能的,因为子进程直接与操作系统交互。

例如,让我们用 C++ 和 Java 创建一个 hello world 程序。 为了执行以下文件,您需要安装 C++ 和 Java 编译器。

helloworld.cpp

 #include <iostream> int main(){ std::cout << "This is a hello world in C++" << std::endl; return 0; }


你好世界

class HelloWorld{ public static void main(String args[]){ System.out.println("This is a hello world in Java"); } }


我知道与简单的 Python one-liner 相比,这似乎是很多代码,但这仅用于测试目的。

我们将创建一个 Python 脚本,该脚本运行目录中的所有 C++ 和 Java 文件。 首先,我们希望根据文件扩展名获取文件列表,而 glob 允许我们轻松完成!

 from glob import glob # Gets files with each extension java_files = glob('*.java') cpp_files = glob('*.cpp')

之后,我们可以开始使用子进程来执行每种类型的文件。

 for file in cpp_files: process = subprocess.run(f'g++ {file} -o out; ./out', shell=True, capture_output=True, text=True) output = process.stdout.strip() + ' BTW this was runned by Python' print(output) for file in java_files: without_ext = file.strip('.java') process = subprocess.run(f'java {file}; java {without_ext}',shell=True, capture_output=True, text=True) output = process.stdout.strip() + ' A Python subprocess runned this :)' print(output)

一个小技巧就是使用字符串函数strip来修改输出,只得到我们需要的。

注意:运行大型 Java 或 C++ 文件时要小心,因为我们将它们的输出加载到内存中,这可能会产生内存泄漏。

打开外部程序

我们可以通过子进程调用其他程序的二进制文件位置来运行其他程序。

让我们打开我喜欢的网络浏览器勇敢尝试一下。

 import subprocess subprocess.run('brave')

这将打开一个浏览器实例,或者如果您已经运行了浏览器,则只是另一个选项卡。

打开浏览器

与任何其他接受标志的程序一样,我们可以使用它们来产生所需的行为。

 import subprocess subprocess.run(['brave', '--incognito'])

隐身标志

总结

子进程是由另一个进程创建的计算机进程。 我们可以使用 htop 和任务管理器等工具检查计算机正在运行的进程。

Python 有自己的库来处理子进程。 目前, run函数为我们提供了一个简单的界面来创建和管理子流程。

我们可以用它们创建任何类型的应用程序,因为我们直接与操作系统交互。

最后,请记住,最好的学习方法是创建您想使用的东西。