小工具知识网

当前位置: 首页 > 行业知识 > Python标准库 subprocess 模块多进程编程详解

行业知识

Python标准库 subprocess 模块多进程编程详解

2026-11-09 16:36:43 来源:互联网转载

1. Subprocess模块介绍

1.1 基本功能

  • subprocess 模块,允许生成新的进程执行命令行指令,python程序,以及其它语言编写的应用程序, 如 java, c++,rust 应用等。
  • subprocess可连接多个进程的输入、输出、错误管道,并且获取它们的返回码。
  • asyncio也支持subprocess.

    许多知名库都在使用此模块创建进程,以及做为跨语言粘合工具。典型如ansible, celery,selenium 等。

    1.2 与multiprocessing主要区别

    • multiprocessing 创建的子进程的代码也需要开发者实现。
    • subprocess创建的子进程主要用于运行已有指令或应用。

      根据上述主要区别,不难推断出, subprocess创建子进程的用途,主要用于执行非python的外部程序,如windows/linux 命令,C程序,Java程序等,而且可以实现进程通信,多进程管道,以及异步执行等。

      1.3 subprocess 模块主要掌握知识点

      (1)run()方法创建子进程

      (2)stdin, stdout,stderr 的配置,以及管道使用

      (3)Popen API使用。

      (4)进程之间通信

      2 使用run() 方法创建子进程

      2.1 run() 语法

      subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, capture_output=False, shell=False, cwd=None, timeout=None, check=False, encoding=None, text=None, env=None)

      返回值类型:

      subprocess.CompletedProcess

      主要参数:

      • args:表示要执行的命令。必须是以字符串为元素的 list or tuple 。
      • stdin、stdout 和 stderr:子进程的标准输入、输出和错误。其值可以是 subprocess.PIPE、subprocess.DEVNULL、一个已经存在的文件描述符、已经打开的文件对象或者 None。subprocess.PIPE 表示为子进程创建新的管道。subprocess.DEVNULL 表示使用 os.devnull。默认使用的是 None,表示什么都不做。
      • encoding: 如果指定了该参数,则 stdin、stdout 和 stderr 可以接收字符串数据,并以该编码方式编码。否则只接收 bytes 类型的数据。
      • shell:如果该参数为 True,将通过操作系统的 shell 执行指定的命令。
      • check: 如check=true, 当进程退出码为非0时,将生成 CalledProcessError 异常

        2.2 返回对象CompletedProcess的主要属性与方法:

        主要属性:

        • args 执行指令list or tuple
        • returncode 执行完子进程状态码,为0则表明它已经运行完毕,若值为负值 ,表明子进程被终。 为None表示未执行完成。
        • stdout 输出内容,
        • stderr error输出内容

          方法

        • check_returncode() 如果 returncode 是非零值, 将生成异常 CalledProcessError.

          示例

          >>> subprocess.run(["ls", "-l"]) # doesn't capture outputCompletedProcess(args=['ls', '-l'], returncode=0)>>> subprocess.run("exit 1", shell=True, check=True)Traceback (most recent call last):...subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1

          2.3 什么是 stdin, stdout, stderr?

          OS 执行一个shell命令,会自动打开三个标准文件:

          • 标准输入文件(stdin),通常对应终端的键盘;
          • 标准输出文件(stdout), 标准错误输出文件(stderr),这两个文件都对应终端的屏幕。

            进程的I/O操作:

            • 进程将从标准输入文件中得到输入数据
            • 将正常输出数据输出到标准输出文件,
            • 将错误信息送到标准错误文件中。

              标准输入、输出可以重定向, 从ubuntu linux为例

              • 输入重定向: wc
              • 输出重定向: tail a.log > abc.txt , 输出重定向到abc.txt , >> 为追加模式
              • 错误输出重定向: 用 2> 文件名 表示 ,

                如 python demo.py 2>&1, 将把标准错误输出重定向到输出stdout

                使用“ >/dev/null ”符号,将命令执行结果重定向到空设备中,也就是不显示任何信息。

                有时host进程可能修改了输入/输出设备,subprocess将继承,可以手工指定I/O设备

                windows用run()时,args指令中前面要加cmd.exe做为执行器

                cmdTuple =(“cmd.exe”, “/C”, r"del d:\output*.png")

                subprocess.run(cmdTuple)

                如果运行dos命令,前两个参数为 “cmd.exe”, “/C”, 否则报错。

                subprocess.run([‘cmd’, ‘/C’, ‘dir D:\app’])

                也可使用powshell 做执行器, 其格式如下:

                subprocess.run([“powershell”, “-Command”, “dir D:\app”])

                运行 .py文件,无须加 cmd.exe

                subprocess.run([‘python’, ‘demo.py’, ‘5’]) 其中5为参数

                指令也可以用字符串的形式,用shlex来解析为list

                import shlex

                print( shlex.split(“python subp_timer.py 5”))

                subprocess.run(shlex.split(“python subp_timer.py 5”))

                output:

                [‘python’, ‘subp_timer.py’, ‘5’]

                Starting timer of 5 seconds

                …Done!

                linux 使用默认shell做为执行器,也可以指定如用 ‘bash’

                subprocess.run([“bash”, “-c”, “ls /usr/bin | grep pycode”])

                3.Pipe 使用

                Pipe 即管道,可以将两个进程连接起来:上1个进程的stdout 可以做为下1个进程的输入

                cp1 = subprocess.run(['cmd.exe','/C','dir /A:D /B','D:\workplace'],stdout=subprocess.PIPE)print(cp1.stdout.decode('utf-8'))cp2 = subprocess.run(['cmd.exe','/C','find','/I','\"python\"'],input=cp1.stdout,stdout=subprocess.PIPE)print(cp2)

                4.Popen API 使用

                Popen 是 subprocess的核心,底层的子进程的创建和管理都靠它处理,它支持主程序与子进程之间通信。 run()方法只能用于一些简单场合,Popen()更加方便。

                4.1 Popen对象的构造函数:

                class subprocess.Popen(args, bufsize=-1, stdin=None, stdout=None, stderr=None, shell=False, cwd=None, env=None, *, encoding=None)

                常用参数:

                • args:shell命令,可以是字符串或者序列类型(如:list,元组)
                • bufsize:缓冲区大小。当创建标准流的管道对象时使用,默认-1。 0:不使用缓冲区 1:表示行缓冲,仅当universal_newlines=True时可用,也就是文本模式 正数:表示缓冲区大小 负数:表示使用系统默认的缓冲区大小。
                • stdin, stdout, stderr:分别表示程序的标准输入、输出、错误句柄
                • shell:如果该参数为 True,将通过操作系统的 shell 执行指定的命令。通常使用False
                • cwd:用于设置子进程的当前目录。
                • env:用于指定子进程的环境变量。如果 env = None,子进程的环境变量将从父进程中继承。
                • encoding 为stdout的编码,指定后,可自动将bytes内容转为字符串

                  创建一个子进程,然后执行一个简单的命令:

                  实例

                  >>> import subprocess >>> p = subprocess.Popen('ls -l', shell=True) >>> total 164 -rw-r--r-- 1 root root 133 Jul 4 16:25 admin-openrc.sh -rw-r--r-- 1 root root 268 Jul 10 15:55 admin-openrc-v3.sh ... >>> p.returncode>>> p.wait() 0>>> p.returncode
                  Popen(["/usr/bin/git", "commit", "-m", "Fixes a bug."])

                  4.2 Popen 对象支持context

                  with Popen(["ifconfig"], stdout=PIPE) as proc:    log.write(proc.stdout.read())

                  4.3 Popen对象的方法与属性

                  • Popen.poll()

                    检查子进程是否已被终止。设置并返回returncode 属性。否则返回 None。

                  • Popen.wait(timeout=None)

                    等待子进程被终止。设置并返回returncode 属性。

                    如果进程在 timeout 秒后未中断,抛出一个TimeoutExpired 异常,可以安全地捕获此异常并重新等待。

                  • Popen.communicate(input=None, timeout=None) 与进程交互:将数据发送到 stdin。从 stdout 和 stderr 读取数据,

                    communicate() 返回一个 (stdout_data, stderr_data) 元组。如果文件以文本模式打开则为字符串;否则为字节串。

                    proc = subprocess.Popen(...)try:outs, errs = proc.communicate(timeout=15)except TimeoutExpired:proc.kill()outs, errs = proc.communicate()
                    • Popen.send_signal(signal) 发送OS信号
                    • Popen.terminate(), Popen.kill() 终止、杀***进程

                      属性:

                      args, stdin, stdout, stderr, pid, returncode

                      4.5 Popen.stdout的编码问题

                      stdout值为bytes 类型,查看时通常需要转为str, 但windows 命令返回的stdout编码类型可能不是utf-8. 需要使用chardet.detect( bytes_obj) 来检测

                      import chardetimport subprocesscmd = ['cmd.exe','/C', 'ipconfig']pp = subprocess.Popen(cmd,                       stdout=subprocess.PIPE,                       stderr=subprocess.PIPE)out: bytes = pp.stdout.read()encode = chardet.detect(out)['encoding']print(encode)print(out.decode(encode))

                      output:

                      PS D:\workplace\python\test1\multi_thread> py subp_2.pyGB2312Windows IP 配置...

                      5. 与子进程的通信

                      5.1 向子进程输入数据

                      方式1: 通过communicate(input=bytes_obj) 输入参数

                      process = subprocess.Popen(['cmd', '/C', 'findstr','example'], stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE)# 使用input参数传递输入input_data = b"Some input\n subprocess \n example line"out, err = process.communicate(input=input_data)print(out)

                      方式2: 通过Pipe向子进程输入数据: process.stdin.write()

                      process = subprocess.Popen(['cmd', '/C', 'findstr','example'], stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE)# Write to the subprocess's standard inputprocess.stdin.write(b'first line \n 2:some example input\n third line\n')# Close the input streamprocess.stdin.close()out, err = process.communicate()print(out, err)

                      3)获取子进程的输出内容

                      方式1: 使用 process.communicate() 方法获取 output 与 error

                      out, err = process.communicate(), out, err 均为bytes 类型

                      方式2: 直接读 process.stdout 属性, 方式与读文件相同,

                      line = process.stdout.readline()content = process.stdout.read() 

                      示例

                      # 读取子进程的输出cmd = ["ping", "baidu.com"]process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)counter = 0while True:    # Read a line from the subprocess's stdout    line = process.stdout.readline()        # Check if the line is empty, indicating that the subprocess has finished    if not line :        break    if counter > 3:        print(f"terminate process {process.pid}")        process.terminate()   # 强行终止进程        break    counter += 1    print(process.poll())    # 检查进程是否结束    # Process and print the line    print(line, end='')# Wait for the subprocess to finish and get its return codereturn_code = process.wait(2)print(f"Subprocess returned with exit code: {return_code}")print(process.poll())

                      6. 子进程的异步执行

                      asyncio异步模块也提供了 subprocess 类, 好处是避开了GIL锁的限制, 运行速度显著提高

                      import asyncioasync def run(cmd):    proc = await asyncio.create_subprocess_shell(        cmd,        stdout=asyncio.subprocess.PIPE,        stderr=asyncio.subprocess.PIPE)    stdout, stderr = await proc.communicate()    print(f'[{cmd!r} exited with {proc.returncode}]')    if stdout:        print(f'[stdout]\n{stdout.decode()}')    if stderr:        print(f'[stderr]\n{stderr.decode()}')async def main():    await asyncio.gather(        run('python subp_timer.py 2'),    )asyncio.run(main())

                      7. 其它功能

                      7.1 异常处理

                      子进程可能会遇到各种问题,建议使用如下处理异常的代码结构:

                      import subprocesstry:    cmd = ["your_command_here"]    process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)    stdout, stderr = process.communicate()    print(stdout,stderr)except subprocess.CalledProcessError as e:    print(f'Subprocess failed with return code {e.returncode}')except FileNotFoundError:    print('Command not found')

                      7.2 常见问题排查

                      (1)命令不能运行,通常是args 列表有问题。 可先在terminal 测试

                      (2)命令行处理的文件与当前目录不同,

                      (3)进程block问题

                      • communicate()方法是block方法,如果子进程未结束,运行communicate()会造成进程block, 应该使用stdout.read()来读取中间内容。
                      • 如果进程有输入,需要注意提供输入stdin.write()

上一篇:Delphi源码解析与FTP操作实战指南

下一篇:方舟生态服务器怎么进?