python 上下文管理器及自定义原理解析


Posted in Python onNovember 19, 2019

这篇文章主要介绍了python 上下文管理器原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

Python 提供了 with 语法用于简化资源操作的后续清除操作,是 try/finally 的替代方法,实现原理建立在上下文管理器之上。

Python 提供了一个 contextmanager 装饰器,更进一步简化上下管理器的实现方式。

上下文管理器是Python2.5之后才出现的概念。上下文管理器规定了某个对象的使用范围,当进入或者离开了使用范围,都会有相应的一些调用,比如代码块开始时执行一些准备,代码块结束时结束一些操作。它更多的是用于资源的分配和释放上,即在开始时分配资源,结束时释放一些资源。比如在执行数据库查询时要建立连接,查询结束后要释放连接;写文件时要先打开文件,写结束后,要关闭文件等等。还有,就是资源的加锁和解锁,比如在使用多线程时,可能会用到加锁和解锁。

上下文管理器可以通过使用更可读、更精简的代码实现资源的分配与释放。
复制代码

with的使用

对于上下文管理器的使用,最常见的是使用with语句,with语句可构建资源的分配与释放的语法糖。

因为with语句就是为支持上下文管理器而存在的,使用上下文管理协议的方法包裹一个代码块(with语句体)的执行,并为try...except...finally提供了一个方便使用的封装。

一般语法:

def load_data(filename):
  f = file(filename,'w')
  try:
   f.write('test file')
  finally:
   f.close()

使用with:

# 使用with
with open('test.txt', 'w') as f:
  f.write('Python')

通过 with 语句在编写代码时,会使代码变得更加简洁,不用再去关闭文件。

我们并不需要写文件的关闭操作,文件会在使用完后自动关闭。

with的执行原理

实际上,在文件操作时,并不是不需要写文件的关闭,而是文件的关闭操作在 with 的上下文管理器中的协议方法里已经写好了。当文件操作执行完成后, with语句会自动调用上下文管理器里的关闭语句来关闭文件资源。

上下文管理协议(context management protocol)
ContextManager ,上下文是 context 直译的叫法,在程序中用来表示代码执行过程中所处的前后环境。

上下文管理器中有 __enter__ 和 __exit__ 两个方法,以with为例子,__enter__ 方法会在执行 with 后面的语句时执行,一般用来处理操作前的内容。比如一些创建对象,初始化等;__exit__ 方法会在 with 内的代码执行完毕后执行,一般用来处理一些善后收尾工作,比如文件的关闭,数据库的关闭等。

上下文管理协议包括两个方法:

contextmanager.__enter__() 从该方法进入运行时上下文,并返回当前对象或者与运行时上下文相关的其他对象。如果with语句有as关键词存在,返回值会绑定在as后的变量上。

contextmanager.__exit__(exc_type, exc_val, exc_tb) 退出运行时上下文,并返回一个布尔值标示是否有需要处理的异常。如果在执行with语句体时发生异常,那退出时参数会包括异常类型、异常值、异常追踪信息,否则,3个参数都是None。

with语句的语法如下:

with EXPR as VAR:
  BLOCK

with和as是关键词,EXPR就是上下文表达式,是任意表达式(一个表达式,不是表达式列表),VAR是赋值的目标变量。"as VAR"是可选的。

上述语句的底层实现可以这样描述:

mgr = (EXPR)
exit = type(mgr).__exit__ # 并没有调用
value = type(mgr).__enter__(mgr)
exc = True
try:
  try:
    VAR = value # 如果有"as VAR"
    BLOCK
  except:
    # 这里会处理异常
    exc = False
    if not exit(mgr, *sys.exc_info()):
      raise
    # 如果__exit__返回值是false,异常将被传播;如果返回值是真,异常将被终止
finally:
  if exc:
    exit(mgr, None, None, None)

这样with语句的执行过程就很清楚了。

  • 执行上下文表达式,获取上下文管理器
  • 加载上下文管理器的__exit__()方法以备后期调用
  • 调用上下文管理器的__enter__()方法
  • 如果with语句有指定目标变量,将从__enter__()方法获取的相关对象赋值给目标变量
  • 执行with语句体
  • 调用上下文管理器的__exit__()方法,如果是with语句体造成的异常退出,那异常类型、异常值、异常追踪信息将被传给__exit__(),否则,3个参数都是None。

也可以将多个表达式组织在一起。

with A() as a, B() as b:
BLOCK

它等价于

with A() as a: with B() as b: BLOCK

注:多上下文表达式是从python 2.7开始支持的

自定义上下文管理器(模拟with打开文件)

要实现一个自定义的上下文管理器,肯定要实现两个方法,一是进入对象范围时的准备工作,二是离开对象范围时的结束工作。

Python提供了两个类的方法分别实现上述功能:

  • __enter__ 进入对象范围时(一般代码块开始)被调用;
  • __exit__ 离开对象范围时(代码块结束)呗调用;

因此,一个Python类,只要实现了上述两种方法,就可以说是一个上下文管理器。

class MyOpen(object):
  def __init__(self,path,mode):
    # 记录要操作的文件路径和模式
    self.__path = path
    self.__mode = mode
 
  def __enter__(self):
    print('代码执行到了__enter__......')
    # 打开文件
    self.__handle = open(self.__path,self.__mode)
    # 返回打开的文件对象引用, 用来给 as 后的变量f赋值
    return self.__handle
 
  # 退出方法中,用来实现善后处理工作
  def __exit__(self, exc_type, exc_val, exc_tb):
    print('代码执行到了__exit__......')   
    self.__handle.close()
 
# a+ 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。
with MyOpen('test.txt','a+') as f:
  # 创建写入文件
  f.write("Hello Python!!!")
  print("文件写入成功")

通过执行顺序,可以看到文件写入操作执行完之后,自动调用了__exit__方法,做了善后处理工作。

代码执行到了__enter__......
文件写入成功
代码执行到了__exit__...... 
 

__exit__方法的参数

__exit__ 方法中有三个参数,用来接收处理异常,如果代码在运行时发生异常,异常会被保存到这里。

exc_type : 异常类型

exc_val : 异常值

exc_tb : 异常回溯追踪

# 编写两个数做除法的程序,然后给除数穿入0
class MyCount(object):
  # 接收两个参数
  def __init__(self,x, y):
    self.__x = x
    self.__y = y
  # 返回一个地址(实质是被as后的变量接收),实例对象就会执行MyCount中的方法:div()
  def __enter__(self):
    print('代码执行到了__enter__......')
    return self
  def __exit__(self, exc_type, exc_val, exc_tb):
    print("代码执行到了__exit__......")
    if exc_type == None:
      print('程序没问题')
    else:
      print('程序有问题,如果你能你看懂,问题如下:')
      print('Type: ', exc_type)
      print('Value:', exc_val)
      print('TreacBack:', exc_tb)
 
    # 返回值决定了捕获的异常是否继续向外抛出
    # 如果是 False 那么就会继续向外抛出,程序会看到系统提示的异常信息
    # 如果是 True 不会向外抛出,程序看不到系统提示信息,只能看到else中的输出
    return True
 
  def div(self):
    print("代码执行到了除法div")
    return self.__x / self.__y
with MyCount(1, 0) as mc:
  mc.div()

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

Python 相关文章推荐
Python爬虫利用cookie实现模拟登陆实例详解
Jan 12 Python
详解 Python中LEGB和闭包及装饰器
Aug 03 Python
Python探索之爬取电商售卖信息代码示例
Oct 27 Python
详谈pandas中agg函数和apply函数的区别
Apr 20 Python
Python中分支语句与循环语句实例详解
Sep 13 Python
python取数作为临时极大值(极小值)的方法
Oct 15 Python
python 中如何获取列表的索引
Jul 02 Python
pandas实现将日期转换成timestamp
Dec 07 Python
PyCharm取消波浪线、下划线和中划线的实现
Mar 03 Python
Django调用百度AI接口实现人脸注册登录代码实例
Apr 23 Python
matplotlib 使用 plt.savefig() 输出图片去除旁边的空白区域
Jan 05 Python
python中time tzset()函数实例用法
Feb 18 Python
浅析python中while循环和for循环
Nov 19 #Python
django实现web接口 python3模拟Post请求方式
Nov 19 #Python
wxPython+Matplotlib绘制折线图表
Nov 19 #Python
python元组的概念知识点
Nov 19 #Python
python数值基础知识浅析
Nov 19 #Python
基于python实现蓝牙通信代码实例
Nov 19 #Python
使用IDLE的Python shell窗口实例详解
Nov 19 #Python
You might like
关于二级目录拖拽排序的实现(源码示例下载)
2013/04/26 PHP
Codeigniter注册登录代码示例
2014/06/12 PHP
基于PHP的简单采集数据入库程序
2014/07/30 PHP
PHP回溯法解决0-1背包问题实例分析
2015/03/23 PHP
百度地图API使用方法详解
2015/08/25 PHP
phpmailer绑定邮箱的实现方法
2016/12/01 PHP
PHP使用正则表达式实现过滤非法字符串功能示例
2018/06/04 PHP
项目实践之javascript技巧
2007/12/06 Javascript
javascript判断用户浏览器插件安装情况的代码
2011/01/01 Javascript
Egret引擎开发指南之运行项目
2014/09/03 Javascript
浅谈JavaScript的push(),pop(),concat()方法
2016/06/03 Javascript
Mongoose中document与object的区别示例详解
2017/09/18 Javascript
Intellij IDEA搭建vue-cli项目的方法步骤
2018/10/20 Javascript
Python实现简单状态框架的方法
2015/03/19 Python
Python实现多线程HTTP下载器示例
2017/02/11 Python
python脚本替换指定行实现步骤
2017/07/11 Python
python3 发送任意文件邮件的实例
2018/01/23 Python
解决Python3.5+OpenCV3.2读取图像的问题
2018/12/05 Python
django celery redis使用具体实践
2019/04/08 Python
python-docx文件定位读取过程(尝试替换)
2020/02/13 Python
最新2019Pycharm安装教程 亲测
2020/02/28 Python
python 爬取腾讯视频评论的实现步骤
2021/02/18 Python
美国最佳在线航班预订网站:LookupFare
2019/03/26 全球购物
瑞典耳机品牌:URBANISTA
2019/12/03 全球购物
用缩写的指针比较"if(p)" 检查空指针是否可靠?如果空指针的内部表达不是0会怎么样?
2014/01/05 面试题
C#如何允许一个类被继承但是避免这个类的方法被重载?
2015/02/24 面试题
应届毕业生个人自荐信范文
2013/11/30 职场文书
会计求职信范文
2014/05/24 职场文书
党员廉洁自律承诺书
2014/05/26 职场文书
教师个人查摆剖析材料
2014/10/14 职场文书
个人作风建设心得体会
2014/10/22 职场文书
党员自我评价范文2015
2015/03/03 职场文书
装配车间主任岗位职责
2015/04/08 职场文书
Opencv中cv2.floodFill算法的使用
2021/06/18 Python
Android studio 简单计算器的编写
2022/05/20 Java/Android