python--subprocess.Popen()多进程


参考:python中的subprocess.Popen

subprocess.Popen()用法

subprocess.Popen()主要是用来在python中实现多进程程序。例如,在python脚本中,我们需要执行另一个python脚本,或者执行shell命令或者shell脚本,这种情况下就要用到python的多进程方法了。本文仅介绍subprocess.Popen()方法,python多进程还有很多其他方法,参考:多进程–廖雪峰

subprocess.Popen()的使用格式:

class 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字符串或者列表
bufsize0 无缓冲
1 行缓冲
其他正值 缓冲区大小
负值代表采用默认系统缓冲(一般是全缓冲)
executable指定可执行程序。一般情况下我们通过args参数来设置所要运行的程序。
如果将参数shell设为 True,executable将指定程序使用的shell
stdin
stdout
stderr
分别表示程序的标准输入、输出、错误句柄
他们可以设置成文件对象,文件描述符或者PIPE和None
None 没有任何重定向,继承父进程
PIPE 创建管道
preexec_fn用于指定一个可执行对象(callable object),它将在子进程运行之前被调用(unix)
close_fds在windows平台下,如果close_fds被设置为True,则新创建的子进程将不会继承父进程的输入、输出和错误。
我们不能将close_fds设置为True的同时重定向子进程的标准输入、输出与错误。
shell如果参数shell设为true,程序将通过shell来执行,且args输入应当是字符串形式,同shell窗口执行的命令
如果不设置,默认为false,则输入的args应当是字符串列表
cwd设置子进程的当前目录
env字典类型,设置子进程的环境变量。如果env = None,子进程的环境变量将从父进程中继承。
universal_newlineswindows下文本换行符用’\r\n’,而Linux下用 ‘\n’。若设置为True,Python统一把这些换行符当作’\n’来处理。
startupinfo
createionflags
只在windows下用效,被传递给底层的CreateProcess()函数,设置子进程的一些属性
如:主窗口的外观,进程的优先级等等

subprocess.PIPE 可以初始化stdin, stdout或stderr参数。表示与子进程通信的标准流
subprocess.STDOUT 用于初始化stderr参数,表示将错误通过标准输出流输出。

Popen的属性

方法含义
Popen.poll()用于检查子进程是否已经结束,返回returncode。
Popen.wait()等待子进程结束,返回returncode。
如果子进程输出了大量数据到stdout或者stderr的管道,并达到了系统pipe的缓存大小的话,子进程会等待父进程读取管道,而父进程此时正wait着的话,将会产生传说中的死锁,后果非常严重。
因此通常建议使用communicate读取缓存中的数据从而让进程正常释放缓存之后结束。
Popen.communicate(input=None)与子进程进行交互。向stdin发送数据,或从stdout和stderr中读取数据。直到收到EOF,等待子进程结束。
可选参数input指定发送到子进程的参数。
返回一个元组:(stdoutdata, stderrdata)。
如果希望通过进程的stdin向其发送数据,在创建Popen对象的时候,参数stdin必须被设置为PIPE
如果希望从stdout和stderr获取数据,必须将stdout和stderr设置为PIPE
Popen.send_signal(signal)向子进程发送信号。
Popen.terminate()停止(stop)子进程。在windows平台下,该方法将调用Windows API TerminateProcess()来结束子进程。
Popen.kill()杀死子进程。
Popen.stdin如果参数stdin被设置为PIPE,Popen.stdin将返回一个文件对象,否则返回None。
Popen.stdout如果参数stdout被设置为PIPE,Popen.stdout将返回一个文件对象,否则返回 None。
Popen.stderr如果参数stdout被设置为PIPE,Popen.stderr将返回一个文件对象,否则返回 None。
Popen.pid获取子进程的进程ID。
Popen.returncode获取进程的返回值。
如果进程还没有结束,返回None
如果进程结束则返回值为0
如果返回值是其他数值,则执行错误,可打印报错信息查看(stderr)。

子进程结束的判断

首先来看一段代码:

import subprocess as sp
p = sp.Popen(['echo','helloworl.py'], stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE)
print(p.poll())
print('Exit code:', p.returncode)

打印结果如下,在上述代码中我们仅仅创建了一个Popen对象,然后开启了子进程,随后通过poll()和returncode查看子进程的返回码,从结果来看,子进程没有结束主进程就退出了。

None
Exit code: None

下面我们通过读取stdout的内容来看看子进程会不会正常结束:

import subprocess as sp
p = sp.Popen(['echo','helloword.py'], stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE)
print((p.stdout.readlines())[0].decode(), end='')
print(p.poll())
print('Exit code:', p.returncode)

结果如下,可以看到,结果仍然是子进程没有结束。

helloword.py
None
Exit code: None

前面说到,wait会等待子进程结束,那么我们试试执行wait看看:

import subprocess as sp
p = sp.Popen(['echo','helloword.py'], stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE)
print(p.poll())
print(p.wait())
print('Exit code:', p.returncode)

结果可以看到,在wait之前子进程并没有结束,wait执行返回的值0,returncode也是0,表明子进程正常执行并结束了。

None
0
Exit code: 0

虽然wait可以让子进程正常结束,但是如果缓存中太多数据的话会导致死锁,因此我们采用communicate:

import subprocess as sp
p = sp.Popen(['echo','helloword.py'], stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE)
output, err = p.communicate()
print(output.decode('gbk'), end='')
print(err.decode('gbk'))
print(p.poll())
print('Exit code:', p.returncode)

结果如下,可见在communicate之后,子进程正常执行,缓存中的数据被读取出来,poll和returncode都返回0,表明子进程已正常结束。output是子进程执行输出的信息,err是执行异常时的报错信息,此处可以看到报错信息为空,说明子进程顺利执行,没有错误发生。

helloword.py

0
Exit code: 0

今天发现一个有意思的现象,当p.stdout.readlines()读取了内容之后,再使用p.communicate()读取子进程缓存中的内容,发现后者已经没有内容了,代码如下:

import subprocess as sp
p = sp.Popen(['echo','helloworl.py'], stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE)
print(p.poll())
print('Exit code:', p.returncode)
print((p.stdout.readlines())[0].decode(), end='')
output, err = p.communicate()
print(output.decode('gbk'), end='')
print(err.decode('gbk'))
# p.terminate()
print(p.poll())
print('Exit code:', p.returncode)

结果如下:

None
Exit code: None
helloworl.py

0
Exit code: 0

结果可以看出,p.communicate()没有读取到内容,猜测可能是p.stdout.readlines()已经将内容读取出来,因而管道中已经没有内容了,但是前面讲到p.stdout.readlines()读取了内容之后进程并不能正常结束,只有p.communicate()可以让进程正常结束。那么在碰到需要用p.stdout.readlines()的时候,我们可以采用terminate()让进程强制结束。

import subprocess as sp
p = sp.Popen(['echo','helloworl.py'], stdin=sp.PIPE, stdout=sp.PIPE, stderr=sp.PIPE)
print(p.poll())
print('Exit code:', p.returncode)
print((p.stdout.readlines())[0].decode(), end='')
p.terminate()
print(p.poll())
print('Exit code:', p.returncode)

结果如下:

None
Exit code: None
helloworl.py
0
Exit code: 0
### 正确使用和调试 `subprocess.Popen` #### 调试方法 为了有效调试 `subprocess.Popen`,可以通过设置不同的参数并捕获其输出来进行分析。以下是几个常见的调试技巧: 1. **捕获标准输出和错误流** 可以通过将 `stdout` 和 `stderr` 参数分别设置为 `subprocess.PIPE` 来获取子进程的标准输出和错误信息[^2]。 ```python process = subprocess.Popen(['ls', '-l'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) output, error = process.communicate() print("Output:", output.decode()) print("Error:", error.decode()) ``` 2. **检查返回码** 子进程完成后会有一个退出状态码,可通过调用 `.returncode` 属性来查看该状态码是否正常。 ```python exit_code = process.returncode if exit_code != 0: print(f"Process exited with code {exit_code}") ``` 3. **处理阻塞问题** 如果发现程序挂起,可能是由于未正确读取子进程的输出缓冲区导致管道满而阻塞。解决办法之一是逐步读取数据而不是一次性全部读取[^3]。 ```python while True: line = process.stdout.readline().decode() if not line and process.poll() is not None: break print(line.strip()) ``` 4. **指定工作目录** 使用 `cwd` 参数可以让子进程在一个特定的工作目录下运行,这有助于避免路径相关的问题。 ```python process = subprocess.Popen(['ls', '-l'], cwd='/tmp', stdout=subprocess.PIPE) ``` 5. **传递环境变量** 当需要自定义环境变量时,可以利用 `env` 参数传入字典形式的环境配置[^4]。 ```python custom_env = os.environ.copy() custom_env['MY_VAR'] = 'value' process = subprocess.Popen(['printenv', 'MY_VAR'], env=custom_env, stdout=subprocess.PIPE) result = process.stdout.read().strip() print(result.decode()) # 输出应显示 "value" ``` 6. **禁用 Shell 执行模式** 默认推荐不启用 `shell=True`,除非必要才开启它,因为这样能降低安全隐患以及提升效率。 ```python process = subprocess.Popen(['echo', '$HOME'], shell=False, stdout=subprocess.PIPE) home_dir = process.stdout.read().strip() print(home_dir.decode()) # 不会展开 $HOME 成实际路径 ``` 7. **实时日志记录** 对于长时间运行的任务来说,在线打印每条消息到控制台或者写入文件都是不错的选择。 ```python logfile = open('log.txt', 'w') proc = subprocess.Popen( ['long-running-command'], stdout=logfile, stderr=subprocess.STDOUT, universal_newlines=True ) ``` 8. **超时机制** 添加时间限制防止某些操作无限期等待完成。 ```python try: outs, errs = process.communicate(timeout=15) except TimeoutExpired: process.kill() outs, errs = process.communicate() finally: print(outs.decode(), errs.decode()) ``` --- ### § 1. 如何在 Windows 平台上实现跨平台兼容性的 `Popen`? 2. 是否存在更高级别的替代方案代替手动管理 Popen 实例? 3. 怎样优雅地终止由 `subprocess.Popen` 创建的所有子进程树结构? 4. 在多线程环境中如何安全地共享同一个 Popen 进程实例? 5. 面向对象编程风格下封装 Subprocess API 应注意哪些事项?
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值