使用subprocess模块调用子进程并获取输出

发布于:2018-01-12 | 分类:python/vba/cpp


从python2.4开始,内置的subprocess模块可以创建子进程并连接子进程的标准输入/输出/错误,因此可以用来执行外部程序并获取执行结果和输出。本文示例基于Python2.7,转为Python3代码时需要考虑对bytes类型返回值的decode()转码。

Popen类方法

subprocess模块通过Popen类完成创建子进程并与其交互的功能,常见的几个成员函数如下:

  • Popen.poll()检查子进程是否已经结束,未结束返回None,结束返回returncode属性值
  • Popen.wait()Popen.communicate()都会阻塞父进程,直到子进程结束
  • Popen.communicate(input=None)与子进程交互:向stdin发送数据,从stdoutstderr读取数据

创建Popen对象后,主程序不会自动等待子进程完成

以上三个成员函数都可以用于等待子进程返回:while循环配合Popen.poll()Popen.wait()Popen.communicate()。由于后面二者都会阻塞父进程,所以无法 实时 获取子进程输出,而是等待子进程结束后一并输出所有打印信息。另外,Popen.wait()Popen.communicate()分别将输出存放于管道和内存,前者容易超出默认大小而导致死锁,因此不推荐使用。

Popen类属性

Popen类具有三个与输入输出相关的属性:Popen.stdin, Popen.stdoutPopen.stderr,分别对应子进程的标准输入/输出/错误。Python的sys模块定义了标准输入/输出/错误:

sys.stdin  # 标准输入
sys.stdout # 标准输出
sys.stderr # 标准错误信息    

以上三个对象类似于文件流,因此可以使用readline()write()方法进行读写操作。例如打印标准输出的print指令等效于sys.stdout.write()

需要注意的是,除了直接向控制台打印输出外,标准输出/错误的打印存在缓存,为了实时输出打印信息,需要执行

sys.stdout.flush()
sys.stderr.flush()

标准输入/输出/错误参数

Popen类构造函数中的三个参数stdin, stdoutstderr用以指定执行程序的标准输入/输出/错误的文件句柄。它们的值可以是PIPE、文件描述符(正整数)、文件对象或None

  • PIPE表示创建一个连接子进程的新管道,默认值None, 表示不做重定向。
  • 子进程的文件句柄可以从父进程中继承得到。
  • stderr可以设置为STDOUT,表示将子进程的标准错误重定向到标准输出。

参考示例:

 import subprocess

 child1 = subprocess.Popen(["ls","-l"], stdout=subprocess.PIPE)
 child2 = subprocess.Popen(["wc"], stdin=child1.stdout, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
 out = child2.communicate()

其中,subprocess.PIPE为文本流提供一个缓存区,child1的stdout将文本输出到缓存区;随后child2的stdin从该PIPE读取文本,child2的输出文本也被存放在PIPE中,而标准错误信息则重定向到标准输出;最后,communicate()方法从PIPE中读取child2子进程的标准输出和标准错误。

示例

假设被调用的Python代码如下,其中的flush()方法作用为及时清空缓存,以便主程序实时获取其输出信息。

# coding:utf-8
# file: called.py
import time, sys
try:
    # 获取标准输入
    N = int(sys.stdin.readline())   
except ValueError:
    # 标准错误信息
    sys.stdout.write("numeric input required!!\n")
    sys.stdout.flush()
    exit(1)
else:
    for i in range(N):
        # 标准输出
        sys.stdout.write("%d\n" %i)     
        sys.stdout.flush()
        time.sleep(1)
    exit(0)

主程序call.py

import subprocess, shlex
command = "python called.py"
p = subprocess.Popen(shlex.split(command), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
# 为子进程传递参数
p.stdin.write('5\n') 
# 实时获取输出
while p.poll() == None:
    out = p.stdout.readline().strip()
    if out:
        print "sub process output: ", out
# 子进程返回值
print "return code: ", p.returncode
  • stdin=subprocess.PIPE指定子程序输入方式,接着由p.stdin.write('5\n')给定;子程序中使用sys.stdin.readline()获取
  • stderr=subprocess.STDOUT将标准错误重定向到标准输出,于是可以使用p.stdout.readline()统一获取标准输出和标准错误信息

参考资料