Python subprocess 模块

2022/5/12 17:27:27

本文主要是介绍Python subprocess 模块,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

subprocess最早在2.4版本引入。用来生成子进程,通过管道来与他们的输入/输出/错误 进行交互。  

因为是在标准库的,并且是python 实现的,我们可以直接在 python 安装目录中找到他。(python 安装目录 \Lib\subprocess.py)

如果其他你想看的代码,你也可以去对应路径找一找。直接看源码 比到处搜罗信息 靠谱多了。

subprocess 用来替换多个旧模块和函数:

os.system
os.spawn*
os.popen*
popen2.*
commands.*

 

subprocess模块 与 原来接口 os.popen2 的区别 

(child_stdin, child_stdout) = os.popen2("cmd", mode, bufsize)
==>
p = Popen("cmd", shell=True, bufsize=bufsize,
          stdin=PIPE, stdout=PIPE, close_fds=True)
(child_stdin, child_stdout) = (p.stdin, p.stdout)

参数更多 意味着考虑更全面,你能做的事情也更多,能更深度的参与执行过程,与进程进行更复杂的交互。

 

subprocess模块中的核心类: Popen。

它的构造函数如下:

subprocess.Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, 
                 preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, 
                 startupinfo=None, creationflags=0)

参数args可以是字符串或者序列类型(如:list,元组),用于指定进程的可执行文件及其参数。如果是序列类型,第一个元素通常是可执行文件的路径。我们也可以显式的使用executeable参数来指定可执行文件的路径。

参数bufsize 指定 buf 大小,buf 为0 意味着无缓冲。buf 为1 意味着行缓冲,其他正数意味着缓冲区大小。负数意味着 系统默认的全缓冲。 默认缓冲区大小跟不同系统运行时实现有关。

为了使程序的运行效率最高,流对象通常会提供缓冲区,以减少调用系统I/O接口的调用次数。
缓冲方式存在三种,分别是:
 (1)全缓冲。输入或输出缓冲区被填满,会进行实际I/O操作。其他情况,如强制刷新、进程结束也会进行实际I/O操作。
对于读操作来说,当读入内容的字节数等于缓冲区大小或者文件已经到达结尾,或者强制刷新,会进行实际的I/O操作,将外存文件内容读入缓冲区;
对于写操作来说,当缓冲区被填满或者强制刷新,会进行实际的I/O操作,缓冲区内容写到外存文件中。磁盘文件操作通常是全缓冲的。
(2)行缓冲。输入或输出缓冲区遇到换行符会进行实际I/O操作。其他与全缓冲相同。
(3)无缓冲。没有缓冲区,数据会立即读入内存或者输出到外存文件和设备上。标准错误输出stderr是无缓冲的,这样能够保证错误信息及时反馈给用户,供用户排除错误。

简单点理解就是
行缓冲:把用户数据存入缓冲区,遇到换行符'\n'则先把缓冲区数据打印到屏幕上;
全缓冲:把用户数据存入缓冲区,缓冲区满了则先把缓冲区数据打印到屏幕上;
无缓冲:直接把用户数据打印到屏幕上。

参数stdin, stdout, stderr分别表示程序的标准输入、输出、错误句柄。他们可以是PIPE,文件描述符或文件对象,也可以设置为None,表示从父进程继承。

参数preexec_fn 表示可以在 子进程执行之前调用的 方法。

参数close_fds windows 上不支持,先跳过。

如果参数shell设为true,程序将通过shell来执行。

参数cwd 子进程工作目录。

参数env是字典类型,用于指定子进程的环境变量。如果env = None,子进程的环境变量将从父进程中继承。

参数universal_newlines 如果设置为True, 各种不同的系统上的回车换行符 '\n','\r\n','\r' 都会被替换成'\n',这里应该主要影响的是缓冲区。

参数startupinfo 和 creationflags 会被传递给 CreateProcess,用来指定 子进程的相关参数。比如进程优先级 如果子进程有窗体 则可以用来指定窗体的外观等

 

subprocess.PIPE

  在创建Popen对象时,subprocess.PIPE可以初始化stdin, stdout或stderr参数。表示与子进程通信的标准流。

subprocess.STDOUT,subprocess.STDIN,subprocess.STDERR

  创建Popen对象时,用于初始化相应的stdin,stdout,stderr 等参数,表示将错误通过 标准输出流 输出。

相关方法

1、poll()  #定时检查命令有没有执行完毕,执行完毕后设置并返回returncode属性,没有执行完毕返回None  

>>> res = subprocess.Popen("sleep 10;echo 'hello'",shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) 
>>> print(res.poll()) 
None 
>>> print(res.poll()) 
None 
>>> print(res.poll()) 
0

2、wait()  #等待命令执行完成,并且返回结果状态

>>> obj = subprocess.Popen("sleep 10;echo 'hello'",shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) 
>>> obj.wait() # 中间会一直等待

3、communicate(input=None) #与进程交互 向stdin写数据 从stdout stderr 读数据 等待命令执行完成,返回stdout和stderr 元组。

对于 wait() 官方提示:
Warning This will deadlock when using stdout=PIPE and/or stderr=PIPE and the child process generates enough output to a pipe 
such that it blocks waiting for the OS pipe buffer to accept more data. Use communicate() to avoid that.
即当stdout/stdin设置为PIPE时,使用wait()可能会导致死锁。因而建议使用communicate。

而对于communicate,文档又给出:
Note that if you want to send data to the process’s stdin, you need to create the Popen object with stdin=PIPE. 
Similarly, to get anything other thanNone in the result tuple, you need to give stdout=PIPE and/or stderr=PIPE too.
Note:The data read is buffered in memory, so do not use this method if the data size is large or unlimited.
communicate会把数据读入内存缓存下来,所以当数据很大或者是无限的数据时不要使用。

那么问题来了:
当你使用Python的subprocess.Popen实现命令行之间的管道传输,同时数据源又非常大(比如读取上GB的文本或者无尽的网络流)时,
官方文档不建议用wait,同时communicate还可能把内存撑爆,我们该怎么操作?

来自:https://zhuanlan.zhihu.com/p/430904623

对于这个问题,查看communicate()方法会发现,communicate() 终究会调用 wait() 来等待进程结束,区别在于 communicate 会主动通过stdout.read()来及时读取输出,只不过是读到内存里。

那么我们的解决方案就来了,不用管道,直接写入临时文件就完了啊

from subprocess import Popen
from tempfile import TemporaryFile
with TemporaryFile(mode='w+') as f:  # 使用临时文件保存输出结果
    with Popen('result.txt', shell=True, stdout=f, stderr=subprocess.STDOUT) as proc:
        status = proc.wait()

4、terminate()  #结束进程  在windows平台下,该方法将调用Windows API TerminateProcess()来结束子进程。

import subprocess 
>>> res = subprocess.Popen("sleep 20;echo 'hello'",shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) 
>>> res.terminate() # 结束进程 

5、pid  #获取当前执行子shell的程序的pid

import subprocess 
>>> res = subprocess.Popen("sleep 5;echo 'hello'",shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) 
>>> res.pid # 获取这个linux shell 的 进程号 2778

6、Popen.returncode  #获取进程的返回值。如果进程还没有结束,返回None。

7、Popen.send_signal(signal)  #向子进程发送信号。 调用 os.kill(signal) 来结束进程

8、Popen.kill()  #杀死子进程。 实际上就是调用 send_signal(signal.SIGKILL)

 

subprocess 的快捷使用的方法

call(*popenargs, **kwargs): 、check_call(*popenargs, **kwargs):、check_output(*popenargs, **kwargs):

这三个方法 归根结底都是调用 subprocess.Popen() ,不同的是对于调用之后结果的处理

def call(*popenargs, **kwargs):
    return Popen(*popenargs, **kwargs).wait()

def check_output(*popenargs, **kwargs):
    if 'stdout' in kwargs:
        raise ValueError('stdout argument not allowed, it will be overridden.')
    process = Popen(stdout=PIPE, *popenargs, **kwargs)
    output, unused_err = process.communicate()
    retcode = process.poll()
    if retcode:
        cmd = kwargs.get("args")
        if cmd is None:
            cmd = popenargs[0]
        raise CalledProcessError(retcode, cmd, output=output)
    return output

在安全性上讲也是,在源码中是这么说的:

Unlike some other popen functions, this implementation will never call
/bin/sh implicitly.  This means that all characters, including shell
metacharacters, can safely be passed to child processes.

就是说  subprocess 不会直接调用 /bin/sh, 所有字符都会经过安全的处理 才会传递给子进程。

如果要在子进程刷新其stdout缓冲区后逐行获取子进程的输出:

#!/usr/bin/env 
from subprocess import Popen, PIPE 
with Popen(["python test.py", "args"], stdout=PIPE, bufsize=1, universal_newlines=True) as p: 
    for line in p.stdout: 
        print(line, end='!')

 

我的python版本

Python 2.7.2 (default, Jun 12 2011, 14:24:46) [MSC v.1500 64 bit (AMD64)] on win32

 



这篇关于Python subprocess 模块的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程