zoukankan      html  css  js  c++  java
  • Python多进程(1)——subprocess与Popen()

      Python多进程方面涉及的模块主要包括:

      本文主要介绍 subprocess 模块及其提供的 Popen 类,以及如何使用该构造器在一个进程中创建新的子进程。此外,还会简要介绍 subprocess 模块提供的其他方法与属性,这些功能上虽然没有 Popen 强大的工具,在某些情况下却非常方便和高效。

      本文的目录如下:

      1. subprocess.Popen 类

      2. Popen 对象的属性

      3. Popen 对象的方法

      4. subprocess模块的其他简便方法

      5. subprocess模块的其他属性

      6. subprocess模块定义的异常

    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)

      创建并返回一个子进程,并在这个进程中执行指定的程序。

      实例化 Popen 可以通过许多参数详细定制子进程的环境,但是只有一个参数是必须的,即位置参数 args ,下面也会详细介绍剩余的具名参数。

    参数介绍:

    • args:要执行的命令或可执行文件的路径。一个由字符串组成的序列(通常是列表),列表的第一个元素是可执行程序的路径,剩下的是传给这个程序的参数,如果没有要传给这个程序的参数,args 参数可以仅仅是一个字符串。
    • bufsize:控制 stdin, stdout, stderr 等参数指定的文件的缓冲,和打开文件的 open()函数中的参数 bufsize 含义相同。
    • executable:如果这个参数不是 None,将替代参数 args 作为可执行程序;
    • stdin:指定子进程的标准输入;
    • stdout:指定子进程的标准输出;
    • stderr:指定子进程的标准错误输出;

      对于 stdin, stdoutstderr 而言,如果他们是 None(默认情况),那么子进程使用和父进程相同的标准流文件。

      父进程如果想要和子进程通过 communicate() 方法通信,对应的参数必须是 subprocess.PIPE(见下文例4);

      当然 stdin, stdoutstderr 也可以是已经打开的 file 对象,前提是以合理的方式打开,比如 stdin 对应的文件必须要可读等。 

    • preexec_fn:默认是None,否则必须是一个函数或者可调用对象,在子进程中首先执行这个函数,然后再去执行为子进程指定的程序或Shell。
    • close_fds:布尔型变量,为 True 时,在子进程执行前强制关闭所有除 stdin,stdout和stderr外的文件;
    • shell:布尔型变量,明确要求使用shell运行程序,与参数 executable 一同指定子进程运行在什么 Shell 中——如果executable=None 而 shell=True,则使用 /bin/sh 来执行 args 指定的程序;也就是说,Python首先起一个shell,再用这个shell来解释指定运行的命令。
    • cwd:代表路径的字符串,指定子进程运行的工作目录,要求这个目录必须存在;
    • env:字典,键和值都是为子进程定义环境变量的字符串;
    • universal_newline:布尔型变量,为 True 时,stdout 和 stderr 以通用换行(universal newline)模式打开,
    • startupinfo:见下一个参数;
    • creationfalgs:最后这两个参数是Windows中才有的参数,传递给Win32的CreateProcess API调用。

      同 Linux 中创建子进程类似,父进程创建完子进程后,并不会自动等待子进程执行,父进程在子进程之前推出将导致子进程成为孤儿进程,孤儿进程统一由 init 进程接管,负责其终止后的回收工作。

      如果父进程在子进程之后终止,但子进程终止时父进程没有进行最后的回收工作,子进程残留的数据结构称为僵尸进程。大量僵尸进程将耗费系统资源,因此父进程及时等待和回收子进程是必要的,除非能够确认自己比子进程先终止,从而将回收工作过渡给 init 进程。

      这个等待和回收子进程的操作就是wait()函数,下文中将会介绍。

    例1:

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

    >>> 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
    0
    

      这里也可以使用 p = subprocess.Popen(['ls', '-cl']) 来创建子进程。

    Popen 对象的属性

      Popen创建的子进程有一些有用的属性,假设 p 是 Popen 创建的子进程,p 的属性包括:

    1. 

    p.pid

      子进程的PID。

     2. 

    p.returncode

      该属性表示子进程的返回状态,returncode可能有多重情况:

    • None —— 子进程尚未结束;
    • ==0 —— 子进程正常退出;
    • > 0—— 子进程异常退出,returncode对应于出错码;
    • < 0—— 子进程被信号杀掉了。

     3. 

    p.stdin, p.stdout, p.stderr

      子进程对应的一些初始文件,如果调用Popen()的时候对应的参数是subprocess.PIPE,则这里对应的属性是一个包裹了这个管道的 file 对象,

      

    Popen 对象的方法

    1.

    p.poll()

      检查子进程  p 是否已经终止,返回 p.returncode 属性 (参考下文 Popen 对象的属性);

    2.

    p.wait()

      等待子进程 p 终止,返回 p.returncode 属性;

      注意:

        wait() 立即阻塞父进程,直到子进程结束!

    3.

    p.communicate(input=None)

      和子进程 p 交流,将参数 input (字符串)中的数据发送到子进程的 stdin,同时从子进程的 stdout 和 stderr 读取数据,直到EOF。

      返回值:

        二元组 (stdoutdata, stderrdata) 分别表示从标准出和标准错误中读出的数据。

      父进程调用 p.communicate() 和子进程通信有以下限制:

      (1) 只能通过管道和子进程通信,也就是说,只有调用 Popen() 创建子进程的时候参数 stdin=subprocess.PIPE,才能通过 p.communicate(input) 向子进程的 stdin 发送数据;只有参数 stout 和 stderr 也都为 subprocess.PIPE ,才能通过p.communicate() 从子进程接收数据,否则接收到的二元组中,对应的位置是None。

      (2)父进程从子进程读到的数据缓存在内存中,因此commucate()不适合与子进程交换过大的数据。

      注意:

        communicate() 立即阻塞父进程,直到子进程结束!

    4.

    p.send_signal(signal)

      向子进程发送信号 signal

    5.

    p.terminate()

      终止子进程 p ,等于向子进程发送 SIGTERM 信号;

    6.

    p.kill()

      杀死子进程 p ,等于向子进程发送 SIGKILL 信号;

    subprocess模块的其他方法

    1. 

    subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False)

      父进程直接创建子进程执行程序,然后等待子进程完成

      返回值:

        call() 返回子进程的 退出状态 即 child.returncode 属性;

    2. 

    subprocess.check_call(args, *, stdin=None, stdout=None, stderr=None, shell=False)

      父进程直接创建子进程执行程序,然后等待子进程完成,具体可以使用的参数,参考上文 Popen 类的介绍。

      返回值:

        无论子进程是否成功,该函数都返回 0;但是 

      如果子进程的退出状态不是0,check_call() 抛出异常 CalledProcessError,异常对象中包含了 child.returncode 对应的返回码。

    例2:

      check_call()正常与错误执行命令

    >>> p = subprocess.check_call(['ping' ,'-c', '2', 'www.baidu.com'])
    PING www.a.shifen.com (220.181.111.188) 56(84) bytes of data.
    64 bytes from 220.181.111.188: icmp_seq=1 ttl=42 time=37.4 ms
    64 bytes from 220.181.111.188: icmp_seq=2 ttl=42 time=37.3 ms
    
    --- www.a.shifen.com ping statistics ---
    2 packets transmitted, 2 received, 0% packet loss, time 1001ms
    rtt min/avg/max/mdev = 37.335/37.410/37.486/0.207 ms
    >>> print p
    0
    >>> p = subprocess.check_call(['ping' ,'-c', '4', 'www.!@$#@!(*^.com'])
    ping: unknown host www.!@$#@!(*^.com
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/usr/lib/python2.7/subprocess.py", line 540, in check_call
        raise CalledProcessError(retcode, cmd)
    subprocess.CalledProcessError: Command '['ping', '-c', '4', 'www.!@$#@!(*^.com']' returned non-zero exit status 2
    >>> print p
    0
    

      

    3.

    subprocess.check_output(args, *, stdin=None, stderr=None, shell=False, universal_newlines=False)

      父进程直接创建子进程执行程序,以字符串的形式返回子进程的输出。

      返回值:

        字符串形式的子进程的输出结果,但是,

      如果子进程的 退出状态 不是0,那么抛出异常 CalledProcessError,异常对象中包含了 child.returncode 对应的返回码。

      注意:

        check_output() 的函数签名中没有参数 stdout,调用该方法时,子进程的输出默认就返回给父进程。

    例3:

      check_output() 调用的子进程正常与错误退出

    >>> subprocess.check_output(["echo", "Hello World!"])
    'Hello World!
    '
    
    >>> subprocess.check_output("exit 1", shell=True)
    Traceback (most recent call last):
       ...
    subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1
    

    注意:

      使用上面提到的三个方法:call()、check_call() 和 check_output() 时,尽量不要将参数 stderr stdout 设置为 subprocess.PIPE,这几个函数默认都会等待子进程完成,子进程产生大量的输出数据如果造成管道堵塞,父进程再等待子进程完成可能造成死锁。

      

    subprocess模块的其他属性

    subprocess.PIPE

      调用本模块提供的若干函数时,作为 std* 参数的值,为标准流文件打开一个管道。

    例4:

      使用管道连接标准流文件

    import subprocess
    child1 = subprocess.Popen(['ls', '-l'], stdout=subprocess.PIPE)
    child2 = subprocess.Popen(['wc', '-l'], stdin=child1.stdout, stdout=subprocess.PIPE)
    out = child2.communicate()
    child1.wait()
    child2.wait()
    print(out)

      这里将子进程 child1 的标准输出作为子进程 child2 的标准输入,父进程通过 communicate() 读取 child2 的标准输出后打印。

    subprocess.STDOUT

      调用本模块提供的若干函数时,作为 stderr 参数的值,将子进程的标准错误输出打印到标准输出。

    subprocess模块定义的异常

    exception subprocess.CalledProcessError

      (1)什么时候可能抛出该异常:调用 check_call() 或 check_output() ,子进程的退出状态不为 0 时。

      (2)该异常包含以下信息:

    • returncode:子进程的退出状态;
    • cmd:创建子进程时指定的命令;
    • output:如果是调用 check_output() 时抛出的该异常,这里包含子进程的输出,否则该属性为None。

      总结

      本文介绍了Python subprocess的基本用法,使用 Popen 可以在Python进程中创建子进程,如果只对子进程的执行退出状态感兴趣,可以调用 subprocess.call() 函数,如果想通过异常处理机制解决子进程异常退出的情形,可以考虑使用 subprocess.check_call() 和 subprocess.check_output。如果希望获得子进程的输出,可以调用 subprocess.check_output(),但 Popen() 无疑是功能最强大的。

      subprocess模块的缺陷在于默认提供的父子进程间通信手段有限,只有管道;同时创建的子进程专门用来执行外部的程序或命令。

      Linux下进程间通信的手段很多,子进程也完全可能从创建之后继续调用

  • 相关阅读:
    2. Add Two Numbers
    1. Two Sum
    leetcode 213. 打家劫舍 II JAVA
    leetcode 48. 旋转图像 java
    leetcode 45. 跳跃游戏 II JAVA
    leetcode 42. 接雨水 JAVA
    40. 组合总和 II leetcode JAVA
    24. 两两交换链表中的节点 leetcode
    1002. 查找常用字符 leecode
    leetcode 23. 合并K个排序链表 JAVA
  • 原文地址:https://www.cnblogs.com/Security-Darren/p/4733368.html
Copyright © 2011-2022 走看看