【Python随笔】掌握子进程subprocess模块的使用方法

python开发期间,很多时候我们会需要执行一段cmd终端命令,或者是执行其他程序返回stdout或者文件输出结果。这种时候,我们就需要用到subprocess模块。虽然我们用os.system也可以达到执行命令的需求,但用os.system只是干发一段命令,对于执行命令的程序,我们没有办法跟踪它的内部状态以及执行结果,因此从稳定性的角度来讲不是一个好的选择。因此,本篇文章讲解下subprocess子进程模块的的基础应用,让没用过这个模块或是经常踩坑的同学都避避坑。

subprocess模块的官方文档在这里,最核心的单位是subprocess.Popen类,它描述了一个正在运行中的进程。subprocess最基础的用法是subprocess.run,我们入参一段cmd终端命令,run方法内部就会启动一个Popen对象执行这个命令,等待命令执行结束后,返回这个命令执行的退出码retcode,标准输出流内容stdout以及标准错误流内容stderr。我们可以从源码中详细捋一下subprocess.run的流程:

def run(*popenargs,
        input=None, capture_output=False, timeout=None, check=False, **kwargs):
    # 忽略上面参数处理部分
    with Popen(*popenargs, **kwargs) as process:  # 新起一个Popen的context
        try:
            # 通过communicate方法拉取最终stdout、stderr的所有数据
            stdout, stderr = process.communicate(input, timeout=timeout)  
        except TimeoutExpired as exc:
            # 超时处理,向os申请杀进程,等待进程结束
            process.kill()
            if _mswindows:
                exc.stdout, exc.stderr = process.communicate()  # communicate也可用来模拟键盘输入
            else:
                process.wait()
            raise
        except:  # Including KeyboardInterrupt, communicate handled that.
            # 向os申请杀进程
            process.kill()
            # We don't call process.wait() as .__exit__ does that for us.
            raise
        retcode = process.poll()  # 获取exitcode
        if check and retcode:
            raise CalledProcessError(retcode, process.args,
                                     output=stdout, stderr=stderr)
    return CompletedProcess(process.args, retcode, stdout, stderr)

subprocess.run是一个阻塞方法,执行了这个接口后,需要等待run入参的命令执行完才能返回。而有些时候,我们需要单独起一个(进程执行)cmd命令,然后周期性每几秒钟去检查命令执行的状态,检查完之后我们还可以在主进程干别的事情,也就是搞一个独立出来的进程。这种情况下,subprocess.run就无法满足,必须直接开subprocess.Popen

一个基本的示例代码如下:

import subprocess
import platform
import os
import signal


def _decode_bytes(_bytes):
    encoding = 'gbk'
    return _bytes.decode(encoding)


def _decode_stream(stream):
    """windows下解码stdout/stderr的数据"""
    if not stream:
        return ''
    return _decode_bytes(stream.read())


# set params
args = ['ping', '127.0.0.1']  # windows这里不用timeout,因为不支持stdin的重定向,用ping大概3~4s的时间总共
working_directory = '.'  # 支持设置命令的工作目录
wait_timeout = 1  # 命令每周期等待时间
cnt, maxcnt = 0, 4  # 等待次数

# run process
print(f'platform system: {platform.system()}')
p = subprocess.Popen(args,
                     cwd=working_directory,
                     # 设置subprocess.PIPE,这样执行完后可以从p.stdout/p.stderr获取输出数据
                     stdout=subprocess.PIPE,
                     stderr=subprocess.PIPE)
print(f'process args: {args}')
print(f'process pid: {p.pid}')
while cnt < maxcnt:
    try:
        p.wait(timeout=wait_timeout)
    except Exception as e:
        print(f'attempt {cnt} -> wait err: {e}')
        cnt += 1
    finally:
        if p.returncode is not None:  # 看是否有退出码,来判断进程是否执行结束
            break

# check retcode
if p.returncode is None:
    print('[ERROR] retcode is None, maybe timeout, try kill process...')
    if platform.system() == 'Windows':  # windows下,强杀进程用taskkill,因为没有SIGKILL
        kill_proc_ret = subprocess.run(['taskkill', '/f', '/pid', str(p.pid)], capture_output=True)
        print(f'[KILLPROC] {_decode_bytes(kill_proc_ret.stdout)}')
    else:  # 其他情况下可以发送SIGKILL
        os.kill(p.pid, signal.SIGKILL)
else:  # 打印返回数据
    retcode, stdout, stderr = p.returncode, _decode_stream(p.stdout), _decode_stream(p.stderr)
    print(f'[OK] retcode: {retcode}\n\tstdout: {stdout}\n\tstderr: {stderr}')

这段程序模拟了周期性等待子进程执行完成的场景,执行完成后拉取stdoutstderr打印,执行超时就强杀进程。基本上关键的地方都有注释,如果有其他类似的场景,可以直接照搬代码。

最后我们也能看到,subprocess本质也是多进程,但和multiprocessing有所不同,multiprocessing是多个python进程,着重于管理多个python进程的运行时环境以及之间的通信;而subprocess则是侧重于去跟踪python程序启动的任意类型进程的状态。两者也有共同点,就是主进程都会持有子进程的handle,只要没调用类似subprocess.run这种阻塞获取子进程状态/结果的接口,在起了新进程后,主进程内都能够随时随地去获取子进程的状态信息。

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SubprocessPython标准库中用于创建新进程的模块。它允许你启动一个新的进程,并与该进程进行交互,包括向其输入数据、从其输出数据等。 其中,communicate()方法Subprocess模块中最常用的方法之一,它用于与子进程进行交互。当你使用Subprocess启动一个新的进程时,你可以在communicate()方法中向该进程输入数据,并在该进程完成后从该进程读取输出数据。 具体来说,communicate()方法向进程的标准输入发送数据,并等待该进程完成后读取其标准输出和标准错误输出。该方法返回一个元组,其中第一个元素表示标准输出,第二个元素表示标准错误输出。 例如,下面的代码展示了如何使用Subprocess模块来启动一个新的进程,并将数据传递给该进程: ``` import subprocess # 启动一个新的进程 process = subprocess.Popen(['python', 'my_script.py'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # 向进程发送数据 process.stdin.write('input data'.encode()) # 等待进程完成并读取其输出数据 output, error = process.communicate() # 打印输出结果 print(output.decode()) ``` 在上面的代码中,我们启动了一个新的进程,并将一个字符串作为输入数据发送给该进程。然后,我们使用communicate()方法等待该进程完成,并读取其标准输出和标准错误输出。最后,我们将输出结果打印出来。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值