• subprocess 官方文档简单整理


    subprocess

    subprocess 模块允许你生成新的进程,连接它们的输入、输出、错误管道,并且获取它们的返回码。此模块打算代替一些老旧的模块与功能:

    os.system
    os.spawn*
    

    run()

    运行被 arg 描述的指令, 等待指令完成,然后返回一个 CompletedProcess 实例.

    这是官方推荐使用的方法。对于更进阶的用例,也可以使用底层的 Popen 接口。

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

    参数:

    • args:默认应该是一个字符串序列,代表命令。如 ['df', '-Th']('df', '-Th') ; 也可以是一个字符串,如 'df -Th',但是此时需要把 shell 参数的值置为 True。通常更推荐使用序列的方式。
    • shell:是否使用 shell 进行执行命令。如果是True,args 参数推荐使用字符串。 同时命令中还支持 shell 管道、文件通配符、环境变量展开以及 ~ 到用户家目录等 shell 功能。在 POSIX,当 shell=True, shell 默认为 /bin/sh
    • capture_output:是否捕获子进程的输出;如果是 True,stdout 和 stdin 将会被捕获(stdout, stdin 参数会自动被设为 subprocess.PIPE),因此 stdoutstderr 参数不应当与 capture_output 同时提供。如果希望将两个流合并在一起捕获,使用 stdout=PIPEstderr=STDOUT 的组合来代替 capture_output 参数。
    • stdin, stdout, stderr: 这三个值是用来设置标准输入,标准输出,标准错误的。默认情况下,子进程会继承父进程的设置,即将输出显示在控制台上,当然你也可以设置为 PIPE (管道),来进行输出和输入的重定向,从而捕获它们的内容。
    • timeout:设置超时时间;如果超时,子进程将被杀死并等待,在子进程中断后抛出 subprocess.TimeoutExpired
    • input:要传给子进程的数据;input 参数将被传递给 Popen.communicate() 以及子进程的 stdin。 如果使用此参数,它必须是一个字节序列。 如果指定了 encodingerrors 又或者将 text 设置为 True,那么 input 也可以是一个字符串。 当使用此参数时,创建的 Popen 对象会自动带上 stdin=PIPE,并且不能再手动指定 stdin 参数。
    • check:是否检查进程的执行状态;如果 check = True, 并且进程以非零状态码退出, 一个 CalledProcessError 异常将被抛出. 这个异常的属性包含:参数, 退出码, 标准输出和标准错误 等信息。
    • encoding:输入和输出的编码方式;如果 encoding 或者 error 被指定, 又或者 text 被设为 True,标准输入/标准输出和标准错误的文件对象将通过指定的 encodingerrors 以文本模式打开, 否则以默认的 io.TextIOWrapper 打开。
    • text:该参数影响的是输入与输出的数据格式,比如它的值默认为False,此时 stdout 和 stderr 输出的是字节序列;当该参数的值设置为True时,stdout 和 stderr 输出的是字符串。
    • universal_newline:这个参数等同于 text 参数并且提供了向后兼容性. 默认情况下, 文件对象是以二进制模式打开的。
    • cwd:覆盖父进程的当前工作路径,给子进程设置工作目录(shell 为 True 才生效)
    • env:None 或者 字典,给子进程设置环境变量(shell 为 True 才生效)

    示例:

    >>> subprocess.run(["ls", "-l"])  # doesn't capture output
    CompletedProcess(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
    
    >>> subprocess.run(["ls", "-l", "/dev/null"], capture_output=True)
    CompletedProcess(args=['ls', '-l', '/dev/null'], returncode=0,
    stdout=b'crw-rw-rw- 1 root root 1, 3 Jan 23 16:23 /dev/null\n', stderr=b'')
    

    CompletedProcess 对象

    这个对象是 run() 函数的返回值。代表一个进程已经结束

    属性:

    • args

      进程执行的命令. 可能是一个列表或字符串.

    • returncode

      子进程的退出状态码. 通常来说, 一个为 0 的退出码表示进程运行正常.一个负值 -N 表示子进程被信号 N 中断 (仅 POSIX).

    • stdout

      从子进程捕获到的标准输出。如果 run() 是设置了 encoding, errors 或者 text=True 则输出的结果是字符串,否则输出的是字节序列。如果没有捕获输出, 则为 None。如果你通过 stderr=subprocess.STDOUT 运行进程,标准输入和标准错误将被组合在这个属性中,并且 stderr 将为 None

    • stderr

      捕获到的子进程的标准错误. 如果 run() 是设置了参数 encoding, errors 或者 text=True 运行的,则输出的是字符串,否则输出的是字节序列。如果未有捕获, 则为 None.

    • check_returncode()

      如果 returncode 非零, 抛出 CalledProcessError

    Popen()

    在一个新的进程中执行子程序,在 POSIX 上,该类会使用类似于 os.execvpe() 的行为来执行子程序。 在 Windows 上,该类会使用 Windows CreateProcess() 函数。默认返回一个 Popen 对象,不会等待进程执行完毕。

    一个更底层的函数,可以更灵活。

    subprocess.Popen(args, bufsize=-1, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=True, shell=False, cwd=None, env=None, universal_newlines=None, startupinfo=None, creationflags=0, restore_signals=True, start_new_session=False, pass_fds=(), *, group=None, extra_groups=None, user=None, umask=- 1, encoding=None, errors=None, text=None, pipesize=- 1)
    

    参数:和 run() 函数相同的参数,用法是一样的,就略过

    • bufsize:管道文件的缓存大小;将在 open() 函数创建了 stdin/stdout/stderr 管道文件对象时作为对应的参数供应:

      • 0 表示不使用缓冲区 (读取与写入是一个系统调用并且可以返回短内容)
      • 1 表示行缓冲(只有 universal_newlines=True 时才有用,例如,在文本模式中)
      • 任何其他正值表示使用一个约为对应大小的缓冲区
      • 负的 bufsize (默认)表示使用系统默认的 io.DEFAULT_BUFFER_SIZE。
    • executable: 一个备用的可执行程序。这个参数很少用到

    • preexec_fn :在子进程执行前先执行的函数。不安全,可能发生死锁。

    • close_fds :如果 True,所有文件描述符除了 0, 1, 2 之外都会在子进程执行前关闭

    • pass_fds:pass_fds 是一个可选的在父子进程间保持打开的文件描述符序列

    • cwd:如果 cwd 不为 None,此函数在执行子进程前会将当前工作目录改为 cwdcwd 可以是一个字符串、字节串或 路径类对象

    • user/group/extra_groups/restore_signals/start_new_session: 这些参数都是 POSIX 上用的,用来设置用户,群组,信号,会话等...

    • creationflags :启动子进程的控制信息,仅在 windows 上有效。如:subprocess.CREATE_NEW_CONSOLE可以设置子进程使用新的控制台而非父进程的控制台:

      subprocess 模块曝出以下 windows 常数。

      • subprocess.STD_INPUT_HANDLE

        标准输入设备,控制台输入缓冲区 CONIN$

      • subprocess.STD_OUTPUT_HANDLE

        标准输出设备。活动控制台屏幕缓冲区 CONOUT$

      • subprocess.STD_ERROR_HANDLE

        标准错误设备。活动控制台屏幕缓冲区 CONOUT$

      • subprocess.SW_HIDE

        隐藏窗口。另一个窗口将被激活。

      • subprocess.STARTF_USESTDHANDLES

        给 StartUpInfo 类使用的常量,表示使用标准句柄处理输入和输出。指明这个类的 STARTUPINFO.hStdInput, STARTUPINFO.hStdOutputSTARTUPINFO.hStdError 属性包含额外的信息。

      • subprocess.STARTF_USESHOWWINDOW

        指明 STARTUPINFO.wShowWindow 属性包含额外的信息。表示显示窗口。

      • subprocess.CREATE_NEW_CONSOLE

        新的进程将有新的控制台,而不是继承父进程的(默认)控制台。

      • subprocess.CREATE_NEW_PROCESS_GROUP

        用于指明将创建一个新的进程组。 这个旗标对于在子进程上使用 os.kill() 来说是必须的。如果指定了 CREATE_NEW_CONSOLE 则这个旗标会被忽略。

      • subprocess.ABOVE_NORMAL_PRIORITY_CLASS

        用于指明一个新进程将具有高于平均的优先级

      • subprocess.BELOW_NORMAL_PRIORITY_CLASS

        用于指明一个新进程将具有低于平均的优先级

      • subprocess.HIGH_PRIORITY_CLASS

        用于指明一个新进程将具有高优先级

      • subprocess.IDLE_PRIORITY_CLASS

        用于指明一个新进程将具有空闲(最低)优先级

      • subprocess.NORMAL_PRIORITY_CLASS

        用于指明一个新进程将具有正常(默认)优先级

      • subprocess.REALTIME_PRIORITY_CLASS

        用于指明一个新进程将具有实时优先级。 你应当几乎永远不使用这个值,因为这会中断管理鼠标输入、键盘输入以及后台磁盘刷新的系统线程。 这个类只适用于直接与硬件“对话”,或者执行短暂任务具有受限中断的应用。

      • subprocess.CREATE_NO_WINDOW

        指明一个新进程将不会创建窗口

      • subprocess.DETACHED_PROCESS

        指明一个新进程将不会继承其父控制台。 这个值不能与 CREATE_NEW_CONSOLE 一同使用。

      • subprocess.CREATE_DEFAULT_ERROR_MODE

        指明一个新进程不会继承调用方进程的错误模式。 新进程会转为采用默认的错误模式。 这个特性特别适用于运行时禁用硬错误的多线程 shell 应用。

      • subprocess.CREATE_BREAKAWAY_FROM_JOB

        指明一个新进程不会关联到任务的 Popen creationflags 形参

    • startupinfo: 用于设置子进程的一些属性,如:主窗口的外观,进程的优先级等等。参数需要是一个 STARTUPINFO 对象,这个对象可以设置一些标志位(和createflags中的标志一样),来控制子进程。

    Popen 对象

    Popen 对象是 Popen() 方法的返回值。

    • Popen.poll()

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

    • Popen.wait(timeout=None)

      等待子进程被终止。终止后设置并返回 returncode 属性。如果进程在 timeout 秒后未中断,抛出一个 TimeoutExpired 异常,可以安全地捕获此异常并重新等待。

      注意:当 stdout=PIPE 或者 stderr=PIPE 时,进程可能会输出非常多的内容到管道缓冲区,将会阻塞进程,或发生死锁。当使用管道时,可以用 Popen.communicate() 来避免阻塞问题

    • Popen.communicate(input=None, timeout=None)

      与进程交互,将数据发送到子进程的 stdin,并从子进程的 stdout 和 stderr 读取数据,直到抵达文件结尾, 等待进程终止,之后设置进程的 returncode 属性。 可选的 input 参数应为要发送到下级进程的数据,如果没有要发送到下级进程的数据则为 None。 如果流是以文本模式打开的(即创建Popen对象时使用了text=True, encoding 等参数),则 input 必须为字符串。 在其他情况下,它必须为字节串。

      communicate() 返回一个 (stdout_data, stderr_data) 元组。如果文件以文本模式打开则为字符串;否则为字节。注意如果你想要向子进程的 stdin 传输数据,你需要通过 stdin=PIPE 参数创建此 Popen 对象。类似的,要从结果元组获取任何非 None 值,你同样需要设置 stdout=PIPE 或者 stderr=PIPE

      如果进程在 timeout 秒后未终止,一个 TimeoutExpired 异常将被抛出。捕获此异常并重新等待将不会丢失任何输出。如果超时到期,子进程不会被杀死,所以为了正确清理一个行为良好的应用程序应该杀死子进程并完成通讯。

    proc = subprocess.Popen(...)
    try:
        outs, errs = proc.communicate(timeout=15)
    except TimeoutExpired:
        proc.kill()  # 杀死进程
        outs, errs = proc.communicate()
    
    注解 内存里数据读取是缓冲的,所以如果数据尺寸过大或无限,不要使用此方法。
    
    • Popen.send_signal(signal)

      将信号 signal 发送给子进程。如果进程已完成则不做任何操作。

      注解: 在 Windows, SIGTERM 是 terminate() 的别名。 CTRL_C_EVENT 和 CTRL_BREAK_EVENT 可以被发送给以包含 CREATE_NEW_PROCESScreationflags 形参启动的进程。

    • Popen.terminate()

      停止子进程。 在 POSIX 操作系统上,此方法会发送 SIGTERM 给子进程。 在 Windows 上则会调用 Win32 API 函数 TerminateProcess() 来停止子进程。

    • Popen.kill()

      杀死子进程。 在 POSIX 操作系统上,此函数会发送 SIGKILL 给子进程。 在 Windows 上 kill() 则是 terminate() 的别名。

    以下属性也是可用的:

    • Popen.args

      args 参数传递给 Popen -- 一个程序参数的序列或者一个简单字符串。

    • Popen.stdin

      如果 stdin 参数为 PIPE,此属性是一个类似 open() 返回的可写的流对象。如果 encodingerrors 参数被指定或者 universal_newlines 参数为 True,则此流是一个文本流,否则是字节流。如果 stdin 参数非 PIPE, 此属性为 None

    • Popen.stdout

      如果 stdout 参数是 PIPE,此属性是一个类似 open() 返回的可读流。从流中读取子进程提供的输出。如果 encodingerrors 参数被指定或者 universal_newlines 参数为 True,此流为文本流,否则为字节流。如果 stdout 参数非 PIPE,此属性为 None

    • Popen.stderr

      如果 stderr 参数是 PIPE,此属性是一个类似 open() 返回的可读流。从流中读取子进程提供的输出。如果 encodingerrors 参数被指定或者 universal_newlines 参数为 True,此流为文本流,否则为字节流。如果 stderr 参数非 PIPE,此属性为 None

    警告

    使用 communicate() 而非 .stdin.write.stdout.read 或者 .stderr.read 来避免由于任意其他 OS 管道缓冲区被子进程填满阻塞而导致的死锁。

    • Popen.pid

      子进程的进程号。注意如果你设置了 shell 参数为 True,则这是生成的子 shell 的进程号。

    • Popen.returncode

      此进程的退出码,由 poll()wait() 设置(以及直接由 communicate() 设置)。一个 None 值 表示此进程仍未结束。一个负值 -N 表示子进程被信号 N 中断 (仅 POSIX).

    声明一个 Popen 对象,仅仅是执行了某个进程,不进行后续操作,进程的状态不会改变:

    >>> import subprocess as s
    
    >>> p = s.Popen("start C:\software", shell=True)       # 执行完毕后,文件夹就已经被打开了,因此进程应该是结束的状态
    >>> p
    <Popen: returncode: None args: 'start C:\\software'>   # 但是进程仍未结束
                
                
    >>> p.communicate()                                    # communicate() 会读取输出,并等待进程结束
    (None, None)
    >>> p
    <Popen: returncode: 0 args: 'start C:\\software'>      # 退出码 0,进程结束了
    
                
    >>> p = s.Popen("start C:\software", shell=True)
    >>> p
    <Popen: returncode: None args: 'start C:\\software'>
    >>> p.wait()                                          # wait() 也能用来等待进程结束
    0
    >>> p
    <Popen: returncode: 0 args: 'start C:\\software'>
                
                
    
    >>> p = s.Popen("start C:\software", shell=True)
    >>> p
    <Popen: returncode: None args: 'start C:\\software'>
    >>> p.poll()                                          # poll() 会判断进程是否结束
    0
    >>> p
    <Popen: returncode: 0 args: 'start C:\\software'>
    

    STARTUPINFO 对象(不常用)

    这个类只能用于 windows 平台,用来控制子进程的窗口和输入输出。它的实例可以作为参数传递给 Popen(startupinfo=...) 来控制子进程的输入输出,是否显示窗口等信息。

    subprocess.STARTUPINFO(*, dwFlags=0, hStdInput=None, hStdOutput=None, hStdError=None, wShowWindow=0, lpAttributeList=None)
    
    • dwFlags

      一个位字段(bit field) 来决定子进程创建窗口时的属性:

    si = subprocess.STARTUPINFO()
    si.dwFlags = subprocess.STARTF_USESTDHANDLES | subprocess.STARTF_USESHOWWINDOW
    
    • hStdInput

      If dwFlags specifies STARTF_USESTDHANDLES, this attribute is the standard input handle for the process. If STARTF_USESTDHANDLES is not specified, the default for standard input is the keyboard buffer.

    • hStdOutput

      如果 dwFlags 指定了 STARTF_USESTDHANDLES,这个属性代表了进程的标准输出处理方式;否则这个参数会被忽略并且标准输出会变成控制台窗口的缓冲区。

    • hStdError

      如果 dwFlags 指定了 STARTF_USESTDHANDLES,这个属性代表了进程的标准错误处理方式;否则这个参数会被忽略并且标准输出会变成控制台窗口的缓冲区。

    • wShowWindow

      如果 dwFlags 指定了 STARTF_USESTDHANDLES,除了 SW_SHOWDEFAULT意外,这个属性可以是 ShowWindow 函数中任何合法的 nCmdShow 值(即一个数字,控制窗口如何显示)。否则这个参数会被忽略。

      subprocess.SW_HIDE 这个常量可以被提供给这个属性,只有当 shell=True 时才会有用。

    • lpAttributeList

      一个包含了额外属性的字典,可以用来给子进程设置属性。

    较旧的高阶API

    call()

    运行由 args 所描述的命令。 等待命令完成,然后返回 returncode 属性。

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

    注解

    请不要在此函数中使用 stdout=PIPEstderr=PIPE。 如果子进程向管道生成了足以填满 OS 管理缓冲区的输出而管道还未被读取时它将会阻塞。

    要想捕获输出,可以使用 run() 方法。

    这个函数相当于:run(...).returncode

    check_call()

    执行并等待命令结束。如果返回码非零则会引发 CalledProcessErrorCalledProcessError 对象将在 returncode 属性中保存返回码并在 output 属性中保存所有输出。

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

    注解

    请不要在此函数中使用 stdout=PIPEstderr=PIPE。 如果子进程向管道生成了足以填满 OS 管理缓冲区的输出而管道还未被读取时它将会阻塞。

    这个函数相当于:run(..., check=True)

    check_output()

    执行命令并返回其输出(也会等待进程执行完毕)。

    subprocess.check_output(args, *, stdin=None, stderr=None, shell=False, cwd=None, encoding=None, errors=None, universal_newlines=None, timeout=None, text=None, **other_popen_kwargs)
    

    如果返回码非零则会引发 CalledProcessErrorCalledProcessError 对象将在 returncode 属性中保存返回码并在 output 属性中保存所有输出。

    这个函数相当于:run(..., check=True, stdout=PIPE).stdout

    CalledProcessError 异常对象

    SubprocessError 的子类,当一个被 check_call()check_output() 函数运行的子进程返回了非零退出码时被抛出。

    • returncode

      子进程的退出状态。如果程序由一个信号终止,这将会被设为一个负的信号码。

    • cmd

      用于创建子进程的指令。

    • output

      子进程的输出, 如果被 run()check_output() 捕获。否则为 None

    • stdout

      对 output 的别名,对应的有 stderr

    • stderr

      子进程的标准错误输出,如果被 run() 捕获。 否则为 None

    可供输出的对象

    在进程中,我们可以将进程的输出定向到下面几中对象上:

    subprocess.DEVNULL

    可被 Popenstdin, stdout 或者 stderr 参数使用的特殊值, 表示使用特殊文件 os.devnull.

    subprocess.PIPE

    可被 Popenstdin, stdout 或者 stderr 参数使用的特殊值, 表示打开标准流的管道. 常用于 Popen.communicate()。管道通常用于父子进程之间进行数据交流,可以将其看作一个文件,子进程往里写入,父进程可以从里面读取。

    subprocess.STDOUT

    可被 Popenstdinstdout 或者 stderr 参数使用的特殊值, 表示标准错误与标准输出使用同一句柄。

    名词解释:

    stdin

    标准输入。在常见的 shell 命令中,一个命令通常需要有相应的输入,stdin 代表了标准输入,譬如我们的键盘,如:

    $ read    # read 命令可以用来从键盘读取输入
    This is input from keyboard    # 这是我输入的内容
    

    stdout, stderr

    标准输出和错误。一个 shell 命令会有相应的输出结果或者报错,比如 ls dir1 会输出路径下的文件,如果 dir1 文件夹不存在,则会报错。而默认的输出和错误信息,都是显示在用户终端显示器上的。

    PIPE

    管道,在 shell 中,我们通常的标准输出和标准错误都是定向到显示器上终端上的,但是我们其实也可以将输出通过'管道',定向到其他地方,譬如传递给其他命令。这样,前一个命令的输出不会显示在显示器的控制台上,而是直接通过管道,作为后续命令的输入内容:

    find . -type f | xargs grep name
    

    所以,管道就是用来在进程间传递数据的。它本质是一个文件,设置缓冲区大小,然后一个进程往里面写,一个进程从里面读取,这样就可以传递数据了。因此上面介绍 Popen() 方法时提到可能会有缓冲区满了,导致阻塞的问题。

    总结

    通篇看下来可能很乱,但其实有用的API就那么几个,下面汇总一下。

    run()

    官方推荐使用的命令。它会执行命令并等待进程执行完毕,返回一个 CompletedProcess 实例,这个实例包含了进行执行的信息和输出内容。(因此这个方法会阻塞住主进程,因为它要等待子进程执行完毕)

    call()

    运行命令。 等待命令完成,返回的是 returncode ,也就是进程的退出状态码。(这个方法也会阻塞主进程)

    check_call()

    执行并等待命令结束。如果返回码非零则会引发 CalledProcessError 异常。 CalledProcessError 对象将在 returncode 属性中保存返回码并在 output 属性中保存所有输出。(也会阻塞主进程)

    check_output()

    执行命令并返回其输出(也会等待进程执行完毕,阻塞主进程)。

    Popen()

    在一个新的进程中执行子程序,在 POSIX 上,该类会使用类似于 os.execvpe() 的行为来执行子程序。 在 Windows 上,该类会使用 Windows CreateProcess() 函数。默认返回一个 Popen 对象,不会等待进程执行完毕。不会阻塞主进程

    返回一个 Popen 对象,这个进程需要手动杀死,如p.kill() 等。

    但是当执行 Popen 对象的方法: p.communicate() 时,如果没有设置超时时间,则communicate() 这个方法会阻塞主进程,因为它会等待进程结束,然后返回一个 (stdout_data, stderr_data) 元组。如果设置了超时时间,一旦超时则会返回一个异常错误TimeoutExpired,但是子进程并没有被杀死

    附录:

    创建一个全新的独立进程(非子进程)

    windows 平台

    import subprocess
    
    subprocess.Popen([sys.executable, __file__],  creationflags=subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP)  # 不使用父进程的控制台,并且在新的进程组中创建进程
    

    POSIX 平台(没试过好不好使):

    import subprocess
    
    p = subprocess.Popen(["sleep", "30"], start_new_session=True)  # 开启新的会话
    
    # 又或者:
    # subprocess.Popen(['nohup', 'my_command'],
    #                  stdout=open('/dev/null', 'w'),
    #                  preexec_fn=os.setpgrp                # 执行进程前,先设置一个新的进程组
    #                  )
    
  • 相关阅读:
    我们需要测试!
    BlueStacks安装教程
    性能测试知多少系统架构分析
    免费去纽约 赢取总额20万美金 立即报名参加微软创新杯全球大学生大赛
    羡慕嫉妒别人只能说明自己不行
    算法和工程?那个重要......
    学习c++的优秀博客(更新ing)
    《启程》
    一点点反思
    提问的技巧
  • 原文地址:https://www.cnblogs.com/wztshine/p/16089719.html
Copyright © 2020-2023  润新知