什么是上下文管理器?

我们常见的with open操作文件,就是一个上下文管理器。如:

with open(file, 'rb') as f:
text = f.read()

那上下文管理器具体的定义是什么呢?

上下文管理器:是指在一段代码执行之前执行一段代码,用于一些预处理工作;执行之后再执行一段代码,用于一些清理工作

比如刚提到的文件操作,打开文件进行读写,读写完之后需要将文件关闭。很明显用到了上下文管理器。主要依靠__enter____exit__这两个”魔术方法”实现。

__enter__(self)

Defines what the context manager should do at the beginning of the block created by the with statement. Note that the return value of enter is bound to the target of the with statement, or the name after the as.

__exit__(self, exception_type, exception_value, traceback)

Defines what the context manager should do after its block has been executed (or terminates). It can be used to handle exceptions, perform cleanup, or do something always done immediately after the action in the block. If the block executes successfully, exception_type, exceptionvalue, and traceback will be None. Otherwise, you can choose to handle the exception or let the user handle it; if you want to handle it, make sure exit_ returns True after all is said and done. If you don’t want the exception to be handled by the context manager, just let it happen.

当我们需要创建一个上下文管理器类型的时候,就需要实现__enter____exit__方法,这对方法称为上下文管理协议(Context Manager Protocol),定义了一种运行时上下文环境。

基本语法

with EXPR as VAR:
BLOCK

这里就是一个标准的上下文管理器的使用逻辑,其中的运行逻辑:

(1)执行EXPR语句,获取上下文管理器(Context Manager)

(2)调用上下文管理器中的__enter__方法,该方法执行一些预处理工作。

(3)这里的as VAR可以省略,如果不省略,则将__enter__方法的返回值赋值给VAR。

(4)执行代码块BLOCK,这里的VAR可以当做普通变量使用。

(5)最后调用上下文管理器中的的__exit__方法。

(6)__exit__方法有三个参数:exc_type, exc_val, exc_tb。如果代码块BLOCK发生异常并退出,那么分别对应异常的type、value 和 traceback。否则三个参数全为None。

(7)__exit__方法的返回值可以为True或者False。如果为True,那么表示异常被忽视,相当于进行了try-except操作;如果为False,则该异常会被重新raise。

如何自己实现上下文管理器?

简单来说,如果一个类中,实现了__enter____exit__方法,那么这个类就是上下文管理器。

class Contextor():
def __enter__(self):
print('程序的预处理开始啦!')
return self # 作为as说明符指定的变量的值 def __exit__(self, exc_type, exc_val, exc_tb):
print('正在进行收尾!') def func(self):
print('程序进行中....') with Contextor() as var:
var.func() # 输出
程序的预处理开始啦!
程序进行中....
正在进行收尾!

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

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

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

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

第二种,会被大多数人所忽略。这里着重介绍下。

在处理异常时,通常都是使用try...except...来进行异常处理的。这就可能会出现在程序的主逻辑中有大量的异常处理代码,这会大大影响程序的可读性。

好的做法可以通过with将异常处理隐藏起来。

我们以1/0举例(1/0必然会抛出错误)

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() # 输出结果
===connect to resource===
===close resource connection===

运行发现,并没有出异常。

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

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

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

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

如何处理自行处理异常

我们以上面的代码为例,在__exit__加入判断异常的逻辑,如果发生异常,则打印异常信息。

class Resource():
def __enter__(self):
print('===connect to resource===')
return self def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type:
print(f'出现异常:{exc_type}:{exc_val}')
print('===close resource connection===')
return True def operate(self):
1/0 with Resource() as res:
res.operate() # 输出
===connect to resource===
出现异常:<class 'ZeroDivisionError'>:division by zero
===close resource connection===

如何更好的使用上下文管理器?

在Python中有一个专门用于实现上下文管理的标准库contextlib

有了 contextlib 创建上下文管理的最好方式就是使用 contextmanager 装饰器,通过 contextmanager 装饰一个生成器函数,yield 语句前面的部分被认为是__enter__() 方法的代码,后面的部分被认为是 __exit__()方法的代码。

我们以打开文件为例:

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)

如果要处理异常,将上面代码改写成下面的样子。

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('test.txt') as file_in:
for line in file_in:
1/0
print(line)

参考

https://juejin.im/post/5c87b165f265da2dac4589cc
https://www.cnblogs.com/linxiyue/p/3855751.html
https://runnerliu.github.io/2018/01/02/pythoncontextmanager/

扫描下面二维码,关注公众号, 每周定期与您分享原创的、有深度的Python知识点

吃透Python上下文管理器的更多相关文章

  1. python 上下文管理器

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 上下文管理器(context manager)是Python2.5开始支持的一种语 ...

  2. Python 上下文管理器和else块

    最终,上下文管理器可能几乎与子程序(subroutine)本身一样重要.目前,我们只了解了上下文管理器的皮毛--Basic 语言有with 语句,而且很多语言都有.但是,在各种语言中 with 语句的 ...

  3. Python上下文管理器

    在Python中让自己创建的函数.类.对象支持with语句,就实现了上线文管理协议.我们经常使用with open(file, "a+") as f:这样的语句,无需手动调用f.c ...

  4. python上下文管理器ContextLib及with语句

    http://blog.csdn.net/pipisorry/article/details/50444736 with语句 with语句是从 Python 2.5 开始引入的一种与异常处理相关的功能 ...

  5. Python上下文管理器 with

    对于系统资源的操作,如:文件操作.数据库操作等,我们往往打开文件.连接数据库后忘了将其close掉,这时就可能会引发异常,因此我们常用的做法是: # coding:utf-8 f = open(&qu ...

  6. Python上下文管理器(Context managers)

    上下文管理器(Context managers) 上下文管理器允许你在有需要的时候,精确地分配和释放资源. 使用上下文管理器最广泛的案例就是with语句了.想象下你有两个需要结对执行的相关操作,然后还 ...

  7. python上下文管理器细读

    test 1 上下文管理器,将生成器转化为上下文管理器 import contextlib @contextlib.contextmanager def a(): print(1) yield pri ...

  8. python 上下文管理器contextlib.ContextManager

    1 模块简介 在数年前,Python 2.5 加入了一个非常特殊的关键字,就是with.with语句允许开发者创建上下文管理器.什么是上下文管理器?上下文管理器就是允许你可以自动地开始和结束一些事情. ...

  9. Python 上下文管理器模块--contextlib

    在 Python 处理文件的时候我们使用 with 关键词来进行文件的资源关闭,但是并不是只有文件操作才能使用 with 语句.今天就让我们一起学习 Python 中的上下文管理 contextlib ...

随机推荐

  1. 【论文阅读】A practical algorithm for distributed clustering and outlier detection

    文章提出了一种分布式聚类的算法,这是第一个有理论保障的考虑离群点的分布式聚类算法(文章里自己说的).与之前的算法对比有以下四个优点: 1.耗时短O(max{k,logn}*n), 2.传递信息规模小: ...

  2. ASP.NET Core +Highchart+ajax绘制动态柱状图

    一.项目介绍利用前端Highchart,以及ajax向后台获取数据,绘制动态柱状图.hightchart其他实例可查看官网文档.[Highchart](https://www.highcharts.c ...

  3. 【NHOI2018】跳伞登山赛

    [题目描述] 某山区有高高低低的 n 个山峰,根据海拔高度的不同,这些山峰由低到高进行了 1 到 n 编号.有 m 条只能单向通行的羊肠小道连接这些山峰.现在,这里要举行一场跳伞登山赛,选手们伞降到某 ...

  4. postgresql更改配置生效问题

    补充:如何确定psql配置文件的路径 ①切换至psql用户,此处为thunisoft. ②确定路径方法很多,此处介绍常用的几种. <1>ps –ef  |grep base 输出结果中 – ...

  5. SpringSecurity代码实现JWT接口权限授予与校验

    通过笔者前两篇文章的说明,相信大家已经知道JWT是什么,怎么用,该如何结合Spring Security使用.那么本节就用代码来具体的实现一下JWT登录认证及鉴权的流程. 一.环境准备工作 建立Spr ...

  6. 【设计模式大法】Iterator模式

    Iterator模式 --一个一个遍历 在Java中的for语句中 i++的作用是让 i 的值在每次循环后自增1,这样就可以访问数组中的下一个元素.下下一个元素.再下下一个元素,也就实现了从头至尾逐一 ...

  7. P3954 成绩

    题目描述 牛牛最近学习了C++入门课程,这门课程的总成绩计算方法是: 总成绩=作业成绩\times 20\%+×20%+小测成绩×30\%+×30%+期末考试成绩\times 50\%×50% 牛牛想 ...

  8. 线程锁&信号量&gil

    线程锁 线程锁的主要目的是防止多个线程之间出现同时抢同一个数据,这会造成数据的流失.线程锁的作用类似于进程锁,都是为了数据的安全性 下面,我将用代码来体现进程锁的作用: from threading ...

  9. Qt的安装

    由于之前用的vs2017是集成c++环境的,加之dev c++ 编码管理起来不是很方便,Mytc (win10不支持) ,所以转而向Qt 开发工具,以下是大概安装过程 下载地址 清华源:https:/ ...

  10. js对象可扩展性和属性的四个特性(下)

    # js对象可扩展性和属性的四个特性(下) 一.前言 再次花时间回顾一下基础,毕竟要想楼建的好,地基就要牢固,嘻嘻! 在开始之前需要具备对prototype.__proto__.constructor ...