本文内容均基于 python 2.7 版本,整理一下这个模块的一些日常套路和 一些要注意的坑,省去大家读官方文档的时间。但还是推荐认真读下 官方文档,并不长

功能和用途

subprocess 是 python 标准库中的一个模块,用于创建子进程和与子进程交互

该模块替换了一些过时的模块和函数

os.system

os.spawn*

os.popen*

popen2.*

commands.*

常见使用姿势

共计四个函数,包括:

三个满足不同需求的简化函数(call, check_call, check_output)

以及一个类 Popen,是上面三个简化函数的基础,也就是说上面三个简化版函数是封装了这个方式的一些特定用法而已

三个 Popen 类的成员函数,用于父子进程间的交互

以下为详细说明

先引入包,为了让下面函数看起来清晰一些,包名临时简化一下为 's'

import subprocess as s

s.call()

创建一个子进程并等待其结束,返回一个 returncode(即 exit code,详见 Linux 基础)

# 函数定义

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

# 举例

>>> subprocess.call(["ls", "-l"])

0

>>> subprocess.call("exit 1", shell=True)

1

s.check_call()

跟上面的 s.call() 类似,只不过变成了 returncode 如果不是 0 的话就抛出异常 subprocess.CalledProcessError,这个对象包含 returncode 属性,可以用 try...except... 来处理(参考 Python 异常处理)

# 函数定义

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

# 举例

>>> subprocess.check_call(["ls", "-l"])

0

>>> subprocess.check_call("exit 1", shell=True)

Traceback (most recent call last):

...

subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1

s.check_output()

创建一个子进程并等待其结束,返回子进程向 stdout 的输出结果。如果 returncode 不为 0,则抛出异常 subprocess.CalledProcessError

# 函数定义

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

# 举例

>>> subprocess.check_output(["echo", "Hello World!"])

'Hello World!\n'

>>> subprocess.check_output("exit 1", shell=True)

Traceback (most recent call last):

...

subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1

s.Popen()

这是整个 subprocess 模块最重要也是最基础的类,以上三个简化方法均是基于这个类。具体每个参数什么含义请看 官方文档

# 类定义

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)

Popen.poll()

检查子进程是否已经结束,返回 returncode

Popen.wait()

阻塞父进程,直到子进程结束,返回 returncode

Popen.communicate(input=None)

阻塞父进程,直到子进程结束。并可以与子进程交互:发送数据(即参数 input,类型必须为 string)到 stdin,以及从 stdout、stderr 读取信息

重点(坑)介绍

先来个开胃菜

以上函数(类)的首个参数 args,有两种使用方式

一个 list,如 ['ls', '-al'],一般 推荐 这种方式

一个字符串,如 'ls -al',这种方式以上函数中的 shell 参数必须为 True。这种方式存在安全风险,具体请看官方文档

再来一盘

要使父进程和子进程可以通信,必须将对应的流的管道(PIPE,自行搜索 Linux 管道)打开,如 stdout=subprocess.PIPE。其中 subprocess.PIPE 是一个特定的值(源码中 hardcode PIPE = -1),当参数列表中某个流被置为了这个值,则将会在创建子进程的同时为这种流创建一个管道对象(调用系统 api,如 p2cread, _ = _winapi.CreatePipe(None, 0)),用于父子进程间的通信

而管道这个东西,其实就是一段内存区间,每个系统内核对其的大小都有所限制,如 linux 上默认最大为 64KB,可以在终端用 ulimit -a 来查看

可选类型还有:

subprocess.PIPE = -1

subprocess.STDOUT = -2 重定向到 stdout

subprocess.DEVNULL = -3 重定向到 /dev/null,即丢弃信息

开始正餐

我们先来看这样一个需求:

脚本 A 需调用一个外部程序 B 来做一些工作,并获取 B 的 stdout 输出

实现方式一:

import subprocess

child = subprocess.Popen(['./B'], stdout=subprocess.PIPE)

child.wait()

print child.stdout

这种方式官方文档中再三警告,会导致死锁问题,不怕坑自己的话就用吧,good luck

导致死锁的原因:

如上文提到,管道的大小是有所限制的,当子进程一直向 stdout 管道写数据且写满的时候,子进程将发生 I/O 阻塞;

而此时父进程只是干等子进程退出,也处于阻塞状态;

于是,GG。

实现方式二(加粗:推荐):

import subprocess

child = subprocess.Popen(['./B'], stdout=subprocess.PIPE)

stdout, stderr = child.communicate()

这是官方推荐的方式,但是也有坑:

看 communicate 的实现

for key, events in ready:

if key.fileobj is self.stdin:

chunk = input_view[self._input_offset :

self._input_offset + _PIPE_BUF]

try:

self._input_offset += os.write(key.fd, chunk)

except BrokenPipeError:

selector.unregister(key.fileobj)

key.fileobj.close()

else:

if self._input_offset >= len(self._input):

selector.unregister(key.fileobj)

key.fileobj.close()

elif key.fileobj in (self.stdout, self.stderr):

data = os.read(key.fd, 32768)

if not data:

selector.unregister(key.fileobj)

key.fileobj.close()

self._fileobj2output[key.fileobj].append(data)

其实 communicate 就是帮我们做了循环读取管道的操作,保证管道不会被塞满;于是子进程可以很爽地写完自己要输出的数据,正常退出,避免了死锁

这里有注意点:

communicate 其实是循环读取管道中的数据(每次 32768 字节)并将其存在一个 list 里面,到最后将 list 中的所有数据连接起来(b''.join(list)) 返回给用户。于是就出现了一个坑:如果子进程输出内容非常多甚至无限输出,则机器内存会被撑爆,再次 GG

第三种实现

第三种的思路其实很简单,就是当子进程的可能输出非常大的时候,直接将 stdout 或 stderr 重定向到文件,避免了撑爆内存的问题。不过这种情形很少见,不常用

Tips:

communicate 的实现中可以看到一些有趣的东西,写一下想法,欢迎讨论

每次读取管道中的数据为 32768bytes,即 32KB。选用这个数据的原因猜想为

取一个折中的值 16KB~64KB,适应不同系统对 PIPE SIZE 最大值的限制(MAC OS 初始默认 16KB,当管道满后自动扩大为 64KB,Linux 默认为 64KB)

32KB 是 512bytes 的整数倍,适应内核对内存的分页机制,提高效率。类似的思路比较常见,如对以太网帧的尾部封装(现已被废弃)

从管道中读取数据并缓存到内存中的操作,并非循环用字符串拼接的方式,而是先将数据分段放进一个 list,最后再连接起来,原因

python 中的每次字符串拼接操作都会生成新对象,当数据量大的时候会有严重的性能问题

而使用 list 合并的方式,仅在每次 list 需要扩容的时候需要将 list 整体搬迁到内存中的另一个空间;以及最后将所有 list 中的元素合并为一个字符串对象的时候生成新对象有性能消耗,相对来说代价非常低

在观察 linux 系统默认管道大小的时候,ulimit -a 的输出中,可以看到 PIPE BUF 4KB 和 PIPE SIZE 64KB 两个值,查了一下了解到,前者是对管道单次写入大小的限制,而后者是管道总大小的限制。前者刚好对应了内核分页单页的大小。

python win32库与subprocess_python subprocess 模块使用(以及详解管道阻塞的坑)相关推荐

  1. pythondifflib详解_用python标准库difflib比较两份文件的异同详解

    [需求背景] 有时候我们要对比两份配置文件是不是一样,或者比较两个文本是否异样,可以使用linux命令行工具diff a_file b_file,但是输出的结果读起来不是很友好.这时候使用python ...

  2. python线程池(threadpool)模块使用笔记详解

    这篇文章主要介绍了python线程池(threadpool)模块使用笔记详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧 最近在做一个视频设备管理的项目,设备包括(摄像 ...

  3. python xlrd课程_python中xlrd模块的使用详解

    一.xlrd的安装 打开cmd输入pip install xlrd安装完成即可 二.xlrd模块的使用 下面以这个工作簿为例 1.导入模块 import xlrd 2.打开工作薄 # filename ...

  4. Python 标准库之 json 编码和解码器『详解』

    Python 标准库之 json 编码和解码器 文章目录 Python 标准库之 json 编码和解码器 一.Python json库介绍 二.导入 json 库 三.Python对应JSON数据类型 ...

  5. Python数据分析库pandas高级接口dt的使用详解

    Series对象和DataFrame的列数据提供了cat.dt.str三种属性接口(accessors),分别对应分类数据.日期时间数据和字符串数据,通过这几个接口可以快速实现特定的功能,本文着重讨论 ...

  6. 细数python标准库中低调的模块

    有没有遇到过这种情况,在网络上搜索如何使用Python进行某种操作,最终找到一个第三方库,直到后来发现标准库中包含的模块或多或少都可以满足你的需求.这种情况并不罕见, 整理了一些python标准库中鲜 ...

  7. Python标准库datetime之date模块详解

    Python标准库datetime之date模块详解 datetime是Python提供的操作日期和时间的标准库,主要有datetime.date模块.datetime.time模块及datetime ...

  8. Python标准库笔记(9) — functools模块

    functools 作用于函数的函数 functools 模块提供用于调整或扩展函数和其他可调用对象的工具,而无需完全重写它们. 装饰器 partial 类是 functools 模块提供的主要工具, ...

  9. Python标准库datetime之datetime模块详解

    Python标准库datetime之datetime模块详解 1.日期时间对象 日期时间对象是指具有日期(年月日)和时间(时分秒)双重属性的实例 日期时间对象的类型为datetime.datetime ...

  10. python中selenium模块驱动谷歌详解

    python中selenium模块驱动谷歌详解 Selenium的介绍.配置和调用 Selenium(浏览器自动化测试框架) 是一个用于Web应用程序测试的工具.Selenium测试直接运行在浏览器中 ...

最新文章

  1. 哪些人适合学习软件测试
  2. Python dataframe指定列顺序输出 + 列数据转化成字符 + 数据框转化成列表
  3. 小甲鱼python课后题答案_Python 小甲鱼教程 课后练习44
  4. java 泛型和集合_Java集合和泛型
  5. 有关jquery checkbox获取checked的问题
  6. Codeforces Round #595 (Div. 3) F. Maximum Weight Subset 树形dp
  7. android 获取已安装 错误代码,android获取手机已经安装的app信息
  8. linux入门指令 详解,Linux基础命令之mktemp详解
  9. 实验7-3-4 字符串替换 (15 分)
  10. 【bug】掘金md文本解析器bug
  11. 关于TP模板的目录设置和渲染问题
  12. PenTool:一个基于Qt的仿真软件Demo
  13. STM32单片机网络通信调试
  14. javascript+css实现走马灯图片轮播器
  15. java企业级应用开发项目总结报告,基于Java软件项目开发岗位的企业实践总结报告...
  16. 在springboot中使用jsp,设置webapp目录时的操作步骤
  17. 小程序钉钉语音录入组件
  18. 长文慎入!经验分享-专科毕业5年,成功入职腾讯!
  19. 【动手学深度学习PyTorch版】19 网络中的网络 NiN
  20. App inventor2通过蓝牙控制Arduino Uno LED开关

热门文章

  1. 脑图管理项目很方便清晰!
  2. iOS在照片上添加水印
  3. 从CCNA到CCNP笔记宝典(第一版)发布了
  4. 9.软件架构设计:大型网站技术架构与业务架构融合之道 --- 高可用与稳定性
  5. 5.Prometheus 监控技术与实践 --- PromQL
  6. 19.docker attach
  7. 141.PHP 对象赋值
  8. 32. PHP Cookies
  9. Android租赁源码,AndroidUS六仔源码出租的配置文件操作封装
  10. android 删除wifi文件,如何删除无线配置文件