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 相关文章推荐
pygame学习笔记(6):完成一个简单的游戏
Apr 15 Python
python连接字符串的方法小结
Jul 13 Python
Python基础知识_浅谈用户交互
May 31 Python
Python内置函数——__import__ 的使用方法
Nov 24 Python
python实现黑客字幕雨效果
Jun 21 Python
vue.js实现输入框输入值内容实时响应变化示例
Jul 07 Python
PythonWeb项目Django部署在Ubuntu18.04腾讯云主机上
Apr 01 Python
python里 super类的工作原理详解
Jun 19 Python
Python ORM编程基础示例
Feb 02 Python
python GUI库图形界面开发之PyQt5布局控件QVBoxLayout详细使用方法与实例
Mar 06 Python
python 如何停止一个死循环的线程
Nov 24 Python
用Python编写简单的gRPC服务的详细过程
Jul 04 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
短波问题解答
2021/02/28 无线电
syphon 虹吸式咖啡冲泡冲煮倒水的得与失
2021/03/03 冲泡冲煮
php 魔术方法使用说明
2009/10/20 PHP
php防注入,表单提交值转义的实现详解
2013/06/10 PHP
三个思路解决laravel上传文件报错:413 Request Entity Too Large问题
2017/11/13 PHP
jquery $.ajax相关用法分享
2012/03/16 Javascript
使用JQuery和CSS模拟超链接的用户单击事件的实现代码
2012/05/23 Javascript
js动态创建、删除表格示例代码
2013/08/07 Javascript
使用jQuery动态加载js脚本文件的方法
2014/04/03 Javascript
jquery遍历函数siblings()用法实例
2015/12/24 Javascript
JS只能输入正整数的简单实例
2016/10/07 Javascript
JavaScript实现Fly Bird小游戏
2016/12/15 Javascript
详解JS中的立即执行函数
2017/02/24 Javascript
JavaScript+Html5实现按钮复制文字到剪切板功能(手机网页兼容)
2017/03/30 Javascript
vue + typescript + 极验登录验证的实现方法
2019/06/27 Javascript
Layui带搜索的下拉框的使用以及动态数据绑定方法
2019/09/28 Javascript
微信小程序音乐播放器开发
2019/11/20 Javascript
Vue中component标签解决项目组件化操作
2020/09/04 Javascript
javascript实现下拉菜单效果
2021/02/09 Javascript
Python实现全局变量的两个解决方法
2014/07/03 Python
Python psutil模块简单使用实例
2015/04/28 Python
python中引用与复制用法实例分析
2015/06/04 Python
Python读写/追加excel文件Demo分享
2018/05/03 Python
用Python实现大文本文件切割的方法
2019/01/12 Python
详解python校验SQL脚本命名规则
2019/03/22 Python
Python+OpenCV+pyQt5录制双目摄像头视频的实例
2019/06/28 Python
解决Numpy中sum函数求和结果维度的问题
2019/12/06 Python
PyTorch中的Variable变量详解
2020/01/07 Python
Python异常原理及异常捕捉实现过程解析
2020/03/25 Python
python爬虫可以爬什么
2020/06/16 Python
致铅球运动员加油稿
2014/02/13 职场文书
2014年高校辅导员工作总结
2014/12/09 职场文书
劳动模范获奖感言
2015/07/31 职场文书
用Python爬取各大高校并可视化帮弟弟选大学,弟弟直呼牛X
2021/06/11 Python
分享3个非常实用的 Python 模块
2022/03/03 Python
【海涛DOTA】D-cup邀请赛NV.cn vs DT.Love
2022/04/01 DOTA