• 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
  • 相关阅读:
    发布在《30天自制操作系统》之前的操作捷径必读贴
    写在《30天自制操作系统》上市之前
    JS跨域代码片段
    C#简单操作XML
    Application Session Cookie ViewState Cache Hidden 区别
    C# string 特殊引用类型
    精通CSS高级Web标准解决方案:相对定位与绝对定位
    精通CSS高级Web标准解决方案:浮动
    精通CSS高级Web标准解决方案:背景图像基础
    char、varchar、nchar、nvarchar的区别
  • 原文地址:https://www.cnblogs.com/lesten/p/Python.html
Copyright © 2020-2023  润新知