Python中的上下文管理器相关知识详解


Posted in Python onSeptember 19, 2019

前言

with 这个关键字,对于每一学习Python的人,都不会陌生。

操作文本对象的时候,几乎所有的人都会让我们要用 with open ,这就是一个上下文管理的例子。你一定已经相当熟悉了,我就不再废话了。

with open('test.txt') as f:
  print f.readlines()

什么是上下文管理器?

基本语法

with EXPR as VAR:
  BLOCK

先理清几个概念

1. 上下文表达式:with open('test.txt') as f:

2. 上下文管理器:open('test.txt')

3. f 不是上下文管理器,应该是资源对象。

如何写上下文管理器?

要自己实现这样一个上下文管理,要先知道上下文管理协议。

简单点说,就是在一个类里,实现了__enter__和__exit__的方法,这个类的实例就是一个上下文管理器。

例如这个示例:

class Resource():
  def __enter__(self):
    print('===connect to resource===')
    return self
  def __exit__(self, exc_type, exc_val, exc_tb):
    print('===close resource connection===')     
  def operate(self):
    print('===in operation===')     
with Resource() as res:
  res.operate()

我们执行一下,通过日志的打印顺序。可以知道其执行过程。
===connect to resource==
===in operation===
===close resource connection===

从这个示例可以很明显的看出,在编写代码时,可以将资源的连接或者获取放在__enter__中,而将资源的关闭写在__exit__ 中。

为什么要用上下文管理器?

学习时多问自己几个为什么,养成对一些细节的思考,有助于加深对知识点的理解。

为什么要使用上下文管理器?

在我看来,这和 Python 崇尚的优雅风格有关。

  • 可以以一种更加优雅的方式,操作(创建/获取/释放)资源,如文件操作、数据库连接;
  • 可以以一种更加优雅的方式,处理异常;

第一种,我们上面已经以资源的连接为例讲过了。

而第二种,会被大多数人所忽略。这里会重点讲一下。

大家都知道,处理异常,通常都是使用 try...execept.. 来捕获处理的。这样做一个不好的地方是,在代码的主逻辑里,会有大量的异常处理代理,这会很大的影响我们的可读性。

好一点的做法呢,可以使用 with 将异常的处理隐藏起来。

仍然是以上面的代码为例,我们将1/0 这个一定会抛出异常的代码写在 operate 里

class Resource():
  def __enter__(self):
    print('===connect to resource===')
    return self
 
  def __exit__(self, exc_type, exc_val, exc_tb):
    print('===close resource connection===')
    return True
 
  def operate(self):
    1/0
 
with Resource() as res:
  res.operate()

运行一下,惊奇地发现,居然不会报错。

这就是上下文管理协议的一个强大之处,异常可以在__exit__ 进行捕获并由你自己决定如何处理,是抛出呢还是在这里就解决了。在__exit__ 里返回 True(没有return 就默认为 return False),就相当于告诉 Python解释器,这个异常我们已经捕获了,不需要再往外抛了。

在 写__exit__ 函数时,需要注意的事,它必须要有这三个参数:

  • exc_type:异常类型
  • exc_val:异常值
  • exc_tb:异常的错误栈信息

当主逻辑代码没有报异常时,这三个参数将都为None。

理解并使用 contextlib

在上面的例子中,我们只是为了构建一个上下文管理器,却写了一个类。如果只是要实现一个简单的功能,写一个类未免有点过于繁杂。这时候,我们就想,如果只写一个函数就可以实现上下文管理器就好了。

这个点Python早就想到了。它给我们提供了一个装饰器,你只要按照它的代码协议来实现函数内容,就可以将这个函数对象变成一个上下文管理器。

我们按照 contextlib 的协议来自己实现一个打开文件(with open)的上下文管理器。

import contextlib
 @contextlib.contextmanager
def open_func(file_name):
  # __enter__方法
  print('open file:', file_name, 'in __enter__')
  file_handler = open(file_name, 'r')
     # 【重点】:yield
  yield file_handler
   # __exit__方法
  print('close file:', file_name, 'in __exit__')
  file_handler.close()
  return
 with open_func('/Users/MING/mytest.txt') as file_in:
  for line in file_in:
    print(line)

在被装饰函数里,必须是一个生成器(带有yield),而yield之前的代码,就相当于__enter__里的内容。yield 之后的代码,就相当于__exit__ 里的内容。

上面这段代码只能实现上下文管理器的第一个目的(管理资源),并不能实现第二个目的(处理异常)。

如果要处理异常,可以改成下面这个样子。

import contextlib
@contextlib.contextmanager
def open_func(file_name):
  # __enter__方法
  print('open file:', file_name, 'in __enter__')
  file_handler = open(file_name, 'r')
  try:
    yield file_handler
  except Exception as exc:
    # deal with exception
    print('the exception was thrown')
  finally:
    print('close file:', file_name, 'in __exit__')
    file_handler.close()
    return
with open_func('/Users/MING/mytest.txt') as file_in:
  for line in file_in:
    1/0
    print(line)

好像只要讲到上下文管理器,大多数人都会谈到打开文件这个经典的例子。

但是在实际开发中,可以使用到上下文管理器的例子也不少。我这边举个我自己的例子。

在OpenStack中,给一个虚拟机创建快照时,需要先创建一个临时文件夹,来存放这个本地快照镜像,等到本地快照镜像创建完成后,再将这个镜像上传到Glance。然后删除这个临时目录。

这段代码的主逻辑是创建快照,而创建临时目录,属于前置条件,删除临时目录,是收尾工作。

虽然代码量很少,逻辑也不复杂,但是“创建临时目录,使用完后再删除临时目录”这个功能,在一个项目中很多地方都需要用到,如果可以将这段逻辑处理写成一个工具函数作为一个上下文管理器,那代码的复用率也大大提高。

代码是这样的

Python中的上下文管理器相关知识详解

总结起来,使用上下文管理器有三个好处:

  • 提高代码的复用率;
  • 提高代码的优雅度;
  • 提高代码的可读性;

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python开发的实用计算器完整实例
May 10 Python
在Python中使用gRPC的方法示例
Aug 08 Python
Python延时操作实现方法示例
Aug 14 Python
在Python中,不用while和for循环遍历列表的实例
Feb 20 Python
python分布式计算dispy的使用详解
Dec 22 Python
Python字典生成式、集合生成式、生成器用法实例分析
Jan 07 Python
Selenium alert 弹窗处理的示例代码
Aug 06 Python
Python三维绘图之Matplotlib库的使用方法
Sep 20 Python
Python Pandas数据分析工具用法实例
Nov 05 Python
python中str内置函数用法总结
Dec 27 Python
如何在向量化NumPy数组上进行移动窗口
May 18 Python
Python  序列化反序列化和异常处理的问题小结
Dec 24 Python
Python Opencv提取图片中某种颜色组成的图形的方法
Sep 19 #Python
pandas read_excel()和to_excel()函数解析
Sep 19 #Python
python openvc 裁剪、剪切图片 提取图片的行和列
Sep 19 #Python
vscode 配置 python3开发环境的方法
Sep 19 #Python
python实现简易学生信息管理系统
Apr 05 #Python
Python字符串大小写转换拼接删除空白
Sep 19 #Python
python BlockingScheduler定时任务及其他方式的实现
Sep 19 #Python
You might like
PHP 5.3.0 安装分析心得
2009/08/07 PHP
PHP使用flock实现文件加锁的方法
2015/07/01 PHP
PHP实现批量检测网站是否能够正常打开的方法
2016/08/23 PHP
PHP基于openssl实现的非对称加密操作示例
2019/01/11 PHP
tp5框架基于Ajax实现列表无刷新排序功能示例
2020/02/10 PHP
如何用javascript判断录入的日期是否合法
2007/01/08 Javascript
基于jQuery的遍历同id元素 并响应事件的代码
2012/06/14 Javascript
js获取网页高度(详细整理)
2012/12/28 Javascript
使用JQuery快速实现Tab的AJAX动态载入(实例讲解)
2013/12/11 Javascript
引入autocomplete组件时JS报未结束字符串常量错误
2014/03/19 Javascript
JS使用replace()方法和正则表达式进行字符串的搜索与替换实例
2014/04/10 Javascript
JavaScript中的函数模式详解
2015/02/11 Javascript
jquery右下角自动弹出可关闭的广告层
2015/05/08 Javascript
Json解析的方法小结
2016/06/22 Javascript
JS简单实现无缝滚动效果实例
2016/08/24 Javascript
使用Javascript监控前端相关数据的代码
2016/10/27 Javascript
bootstrap组件之按钮式下拉菜单小结
2017/01/19 Javascript
jQuery插件FusionCharts绘制的3D双柱状图效果示例【附demo源码】
2017/04/20 jQuery
vue2.0移动端滑动事件vue-touch的实例代码
2018/11/27 Javascript
vue实现滑动超出指定距离回顶部功能
2019/07/31 Javascript
vue cli3适配所有端方案的实现
2020/04/13 Javascript
Python实现3行代码解简单的一元一次方程
2014/08/18 Python
pandas 数据索引与选取的实现方法
2019/06/21 Python
jupyter lab文件导出/下载方式
2020/04/22 Python
吃透移动端 1px的具体用法
2019/12/16 HTML / CSS
英国评分最高的女性剃须刀订阅盒:FFS Beauty
2018/01/25 全球购物
GANT葡萄牙官方商店:拥有美国运动服传统的生活方式品牌
2018/10/18 全球购物
汉语言文学职业规划
2014/02/14 职场文书
理工学院学生自我鉴定
2014/02/23 职场文书
元旦联欢会策划方案
2014/06/11 职场文书
运动会演讲稿100字
2014/08/25 职场文书
死者家属慰问信
2015/03/24 职场文书
销售经理助理岗位职责
2015/04/13 职场文书
领导干部学习三严三实心得体会
2016/01/05 职场文书
十个Python自动化常用操作,即拿即用
2021/05/10 Python
虚拟机linux端mysql数据库无法远程访问的解决办法
2021/05/26 MySQL