找回密码
 会员注册
查看: 36|回复: 0

Python标准库subprocess模块多进程编程详解

[复制链接]

3

主题

0

回帖

10

积分

新手上路

积分
10
发表于 2024-9-8 15:18:35 | 显示全部楼层 |阅读模式
1.Subprocess模块介绍1.1基本功能subprocess模块,允许生成新的进程执行命令行指令,python程序,以及其它语言编写的应用程序,如java,c++,rust应用等。subprocess可连接多个进程的输入、输出、错误管道,并且获取它们的返回码。asyncio也支持subprocess.许多知名库都在使用此模块创建进程,以及做为跨语言粘合工具。典型如ansible,celery,selenium等。1.2与multiprocessing主要区别multiprocessing创建的子进程的代码也需要开发者实现。subprocess创建的子进程主要用于运行已有指令或应用。根据上述主要区别,不难推断出,subprocess创建子进程的用途,主要用于执行非python的外部程序,如windows/linux命令,C程序,Java程序等,而且可以实现进程通信,多进程管道,以及异步执行等。1.3subprocess模块主要掌握知识点(1)run()方法创建子进程(2)stdin,stdout,stderr的配置,以及管道使用(3)PopenAPI使用。(4)进程之间通信2使用run()方法创建子进程2.1run()语法subprocess.run(args,*,stdin=None,input=None,stdout=None,stderr=None,capture_output=False,shell=False,cwd=None,timeout=None,check=False,encoding=None,text=None,env=None)1返回值类型:subprocess.CompletedProcess主要参数:args:表示要执行的命令。必须是以字符串为元素的listortuple。stdin、stdout和stderr:子进程的标准输入、输出和错误。其值可以是subprocess.PIPE、subprocess.DEVNULL、一个已经存在的文件描述符、已经打开的文件对象或者None。subprocess.PIPE表示为子进程创建新的管道。subprocess.DEVNULL表示使用os.devnull。默认使用的是None,表示什么都不做。encoding:如果指定了该参数,则stdin、stdout和stderr可以接收字符串数据,并以该编码方式编码。否则只接收bytes类型的数据。shell:如果该参数为True,将通过操作系统的shell执行指定的命令。check:如check=true,当进程退出码为非0时,将生成CalledProcessError异常2.2返回对象CompletedProcess的主要属性与方法:主要属性:args执行指令listortuplereturncode执行完子进程状态码,为0则表明它已经运行完毕,若值为负值,表明子进程被终。为None表示未执行完成。stdout输出内容,stderrerror输出内容方法check_returncode()如果returncode是非零值,将生成异常CalledProcessError.示例>>>subprocess.run(["ls","-l"])#doesn'tcaptureoutputCompletedProcess(args=['ls','-l'],returncode=0)>>>subprocess.run("exit1",shell=True,check=True)Traceback(mostrecentcalllast):...subprocess.CalledProcessError:Command'exit1'returnednon-zeroexitstatus112345672.3什么是stdin,stdout,stderr?OS执行一个shell命令,会自动打开三个标准文件:标准输入文件(stdin),通常对应终端的键盘;标准输出文件(stdout),标准错误输出文件(stderr),这两个文件都对应终端的屏幕。进程的I/O操作:进程将从标准输入文件中得到输入数据将正常输出数据输出到标准输出文件,将错误信息送到标准错误文件中。标准输入、输出可以重定向,从ubuntulinux为例输入重定向:wcabc.txt,输出重定向到abc.txt,>>为追加模式错误输出重定向:用2>文件名表示,如pythondemo.py2>&1,将把标准错误输出重定向到输出stdout使用“>/dev/null”符号,将命令执行结果重定向到空设备中,也就是不显示任何信息。有时host进程可能修改了输入/输出设备,subprocess将继承,可以手工指定I/O设备windows用run()时,args指令中前面要加cmd.exe做为执行器cmdTuple=(“cmd.exe”,“/C”,r"deld:\output*.png")subprocess.run(cmdTuple)如果运行dos命令,前两个参数为“cmd.exe”,“/C”,否则报错。subprocess.run([‘cmd’,‘/C’,‘dirD:\app’])也可使用powshell做执行器,其格式如下:subprocess.run([“powershell”,“-Command”,“dirD:\app”])运行.py文件,无须加cmd.exesubprocess.run([‘python’,‘demo.py’,‘5’])其中5为参数指令也可以用字符串的形式,用shlex来解析为listimportshlexprint(shlex.split(“pythonsubp_timer.py5”))subprocess.run(shlex.split(“pythonsubp_timer.py5”))output:[‘python’,‘subp_timer.py’,‘5’]Startingtimerof5seconds…Done!linux使用默认shell做为执行器,也可以指定如用‘bash’subprocess.run([“bash”,“-c”,“ls/usr/bin|greppycode”])3.Pipe使用Pipe即管道,可以将两个进程连接起来:上1个进程的stdout可以做为下1个进程的输入cp1=subprocess.run(['cmd.exe','/C','dir/A/B','D:\workplace'],stdout=subprocess.PIPE)print(cp1.stdout.decode('utf-8'))cp2=subprocess.run(['cmd.exe','/C','find','/I','\"python\"'],input=cp1.stdout,stdout=subprocess.PIPE)print(cp2)1234567891011124.PopenAPI使用Popen是subprocess的核心,底层的子进程的创建和管理都靠它处理,它支持主程序与子进程之间通信。run()方法只能用于一些简单场合,Popen()更加方便。4.1Popen对象的构造函数:classsubprocess.Popen(args,bufsize=-1,stdin=None,stdout=None,stderr=None,shell=False,cwd=None,env=None,*,encoding=None)12常用参数:args:shell命令,可以是字符串或者序列类型(如:list,元组)bufsize:缓冲区大小。当创建标准流的管道对象时使用,默认-1。0:不使用缓冲区1:表示行缓冲,仅当universal_newlines=True时可用,也就是文本模式正数:表示缓冲区大小负数:表示使用系统默认的缓冲区大小。stdin,stdout,stderr:分别表示程序的标准输入、输出、错误句柄shell:如果该参数为True,将通过操作系统的shell执行指定的命令。通常使用Falsecwd:用于设置子进程的当前目录。env:用于指定子进程的环境变量。如果env=None,子进程的环境变量将从父进程中继承。encoding为stdout的编码,指定后,可自动将bytes内容转为字符串创建一个子进程,然后执行一个简单的命令:实例>>>importsubprocess>>>p=subprocess.Popen('ls-l',shell=True)>>>total164-rw-r--r--1rootroot133Jul416:25admin-openrc.sh-rw-r--r--1rootroot268Jul1015:55admin-openrc-v3.sh...>>>p.returncode>>>p.wait()0>>>p.returncode12345678Popen(["/usr/bin/git","commit","-m","Fixesabug."])14.2Popen对象支持contextwithPopen(["ifconfig"],stdout=PIPE)asproc:log.write(proc.stdout.read())124.3Popen对象的方法与属性Popen.poll()检查子进程是否已被终止。设置并返回returncode属性。否则返回None。Popen.wait(timeout=None)等待子进程被终止。设置并返回returncode属性。如果进程在timeout秒后未中断,抛出一个TimeoutExpired异常,可以安全地捕获此异常并重新等待。Popen.communicate(input=None,timeout=None)与进程交互:将数据发送到stdin。从stdout和stderr读取数据,communicate()返回一个(stdout_data,stderr_data)元组。如果文件以文本模式打开则为字符串;否则为字节串。proc=subprocess.Popen(...)tryuts,errs=proc.communicate(timeout=15)exceptTimeoutExpired:proc.kill()outs,errs=proc.communicate()123456Popen.send_signal(signal)发送OS信号Popen.terminate(),Popen.kill()终止、杀死进程属性:args,stdin,stdout,stderr,pid,returncode4.5Popen.stdout的编码问题stdout值为bytes类型,查看时通常需要转为str,但windows命令返回的stdout编码类型可能不是utf-8.需要使用chardet.detect(bytes_obj)来检测importchardetimportsubprocesscmd=['cmd.exe','/C','ipconfig']pp=subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE)out:bytes=pp.stdout.read()encode=chardet.detect(out)['encoding']print(encode)print(out.decode(encode))1234567891011outputSD:\workplace\python\test1\multi_thread>pysubp_2.pyGB2312WindowsIP配置...123455.与子进程的通信5.1向子进程输入数据方式1:通过communicate(input=bytes_obj)输入参数process=subprocess.Popen(['cmd','/C','findstr','example'],stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE)#使用input参数传递输入input_data=b"Someinput\nsubprocess\nexampleline"out,err=process.communicate(input=input_data)print(out)12345方式2:通过Pipe向子进程输入数据:process.stdin.write()process=subprocess.Popen(['cmd','/C','findstr','example'],stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE)#Writetothesubprocess'sstandardinputprocess.stdin.write(b'firstline\n2:someexampleinput\nthirdline\n')#Closetheinputstreamprocess.stdin.close()out,err=process.communicate()print(out,err)12345673)获取子进程的输出内容方式1:使用process.communicate()方法获取output与errorout,err=process.communicate(),out,err均为bytes类型方式2:直接读process.stdout属性,方式与读文件相同,line=process.stdout.readline()content=process.stdout.read()12示例#读取子进程的输出cmd=["ping","baidu.com"]process=subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True)counter=0whileTrue:#Readalinefromthesubprocess'sstdoutline=process.stdout.readline()#Checkifthelineisempty,indicatingthatthesubprocesshasfinishedifnotline:breakifcounter>3:print(f"terminateprocess{process.pid}")process.terminate()#强行终止进程breakcounter+=1print(process.poll())#检查进程是否结束#Processandprintthelineprint(line,end='')#Waitforthesubprocesstofinishandgetitsreturncodereturn_code=process.wait(2)print(f"Subprocessreturnedwithexitcode:{return_code}")print(process.poll())123456789101112131415161718192021222324256.子进程的异步执行asyncio异步模块也提供了subprocess类,好处是避开了GIL锁的限制,运行速度显著提高importasyncioasyncdefrun(cmd):proc=awaitasyncio.create_subprocess_shell(cmd,stdout=asyncio.subprocess.PIPE,stderr=asyncio.subprocess.PIPE)stdout,stderr=awaitproc.communicate()print(f'[{cmd!r}exitedwith{proc.returncode}]')ifstdout:print(f'[stdout]\n{stdout.decode()}')ifstderr:print(f'[stderr]\n{stderr.decode()}')asyncdefmain():awaitasyncio.gather(run('pythonsubp_timer.py2'),)asyncio.run(main())123456789101112131415161718192021227.其它功能7.1异常处理子进程可能会遇到各种问题,建议使用如下处理异常的代码结构:importsubprocesstry:cmd=["your_command_here"]process=subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True)stdout,stderr=process.communicate()print(stdout,stderr)exceptsubprocess.CalledProcessErrorase:print(f'Subprocessfailedwithreturncode{e.returncode}')exceptFileNotFoundError:print('Commandnotfound')12345678910111213147.2常见问题排查(1)命令不能运行,通常是args列表有问题。可先在terminal测试(2)命令行处理的文件与当前目录不同,(3)进程block问题communicate()方法是block方法,如果子进程未结束,运行communicate()会造成进程block,应该使用stdout.read()来读取中间内容。如果进程有输入,需要注意提供输入stdin.write()
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2025-1-11 01:23 , Processed in 0.428803 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表