什么是 Python 中的子进程? [5 Usage Examples]

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

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

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

即使您学习编程已有一段时间,子流程的概念也可能看起来晦涩难懂。 本文将深入了解子进程的主要概念,以及如何使用 Python 子进程标准库.

在本教程结束时,您将:

  • 理解子进程的概念
  • 学习了 Python 子进程库的基础知识
  • 通过有用的示例练习您的 Python 技能

让我们开始吧

子进程的概念

广义地说,子流程是 电脑程序 由另一个进程创建。

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

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

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

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

一切始于 /sbin/初始化 这是启动我们计算机上每个进程的命令。 从那时起,我们可以看到其他进程的开始,例如 xfce4-screenshoter 和 xfce4-terminal(这会导致更多的子进程)

看看 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 中引入的,作为一个友好的快捷方式 子进程.Popen. subprocess.run 函数允许我们运行一个命令并等待它完成,与 Popen 相反,我们可以选择稍后调用通信。

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

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

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

这可能看起来太简单了,你是对的。 您想全面了解 shell 带给您的所有功能。 因此,让我们学习如何使用子进程将参数传递给 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 36ndrwxr-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.pyndrwxr-xr-x 8 daniel daniel 4096 may 20 17:31 .gitn-rw-r--r-- 1 daniel daniel 2160 may 17 22:23 .gitignoren-rw-r--r-- 1 daniel daniel 271 may 20 19:53 internet_checker.pyn-rw-r--r-- 1 daniel daniel 1076 may 17 22:23 LICENSEn-rw-r--r-- 1 daniel daniel 216 may 20 22:12 list_dir.pyn-rw-r--r-- 1 daniel daniel 22 may 17 22:23 README.mdn'

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

  8 种最佳加密和比特币警报工具来跟踪价格

在这种情况下,我们希望将输出存储为字符串而不是字节,我们可以通过将文本参数设置为 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。否则,在执行过程中出现问题

  8 款适合您美丽家居的最佳无线安全摄像头

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

import subprocess

import re

programs = input('Separe the programs with a space: ').split()

secure_pattern = '周日'

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 回购)

使用子进程设置 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 ...")

  不要与朋友的 Xbox 共享您的数字游戏

在这种情况下,我们使用多个进程并在我们的 python 脚本中解析我们需要的数据。 我们也在使用 路径库 如果 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++爪哇 编译器。

helloworld.cpp文件

#include <iostream>

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

helloworld.java

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

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

我们将创建一个运行目录中所有 C++ 和 Java 文件的 Python 脚本。 为此,我们首先要根据文件扩展名获取文件列表,然后 球体 让我们轻松做到!

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)

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

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

打开外部程序

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

让我们通过打开我喜欢的网络浏览器 brave 来尝试一下。

import subprocess

subprocess.run('brave')

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

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

import subprocess

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

总结一下

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

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

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

最后,请记住,最好的学习方法是创造一些你喜欢使用的东西。