上下文管理器的使用
通常在涉及确保某种资源以一种期望的方式被初始化和反初始化,或者尽力去避免重复时使用上下文管理器。
编写管理器常见的几个环境场所:
1、资源清理
打开和关闭资源(如文件或者数据库连接)是编写上下文管理器的重要因素之一。确保出现异常时正确关闭资源往往很重要,这样能够避免最终随着时间的推移而产生很多的僵尸进程。
上下文管理器的优势就在于此,通过在__enter__方法中打开资源并返回它,可以保证__enter__方法能执行,同时也能在异常出现之前关闭这个资源。
-看如下打开mysql数据库连接的上下文管理器:
import json import pymysql #todo 单点连接数据库 class DB_LINK(): def __init__(self): self.pool_db = pymysql.connect(host='127.0.0.1',port=3306,user='root',passwd='123456',db='test',charset='utf8',cursorclass=pymysql.cursors.DictCursor) def __enter__(self): self.coon = self.pool_db.cursor() return self.coon def __exit__(self, exc_type, exc_val, exc_tb): self.coon.close() #在上下文管理器中,可以针对数据库执行查询操作并检索结果。 with DB_LINK() as db: db.execute('select * from user where age<2 and username="qxh" and id<500') print(db.fetchall())
[{'id': 52, 'username': 'qxh', 'age': 1}, {'id': 128, 'username': 'qxh', 'age': 1}, {'id': 134, 'username': 'qxh', 'age': 1}, {'id': 229, 'username': 'qxh', 'age': 1}, {'id': 250, 'username': 'qxh', 'age': 1}, {'id': 426, 'username': 'qxh', 'age': 1}]
上下文管理器创建了一个mysql连接对象并且返回另一个指针,开发人员可以使用该指针与数据库交互。当上下文管理器存在时,保证连接是处于关闭状态仍很重要。因为占用数据库的连接不仅消耗内存,而且在应用主机和数据库主机上它们也会打开文件或者端口。此外有些数据库也有最大连接数的阈值。
这个上下文管理器不仅仅在__enter__方法的结尾返回自身,还返回一个数据库的指针。这个示例很实用,但它,执行的仍然是上下午管理器的__exit__方法。
大多数与数据库相关的框架都会处理数据库连接的打开或关闭操作,但原则依然是:
如果打开一个资源,一定要确保正确的关闭它,此时上下文管理器是一个十分优秀的工具。
2、避免重复
当提到避免重复时,异常处理是最为常见的。上下文管理器能够传播和终止异常,这使得最合适将它与except子句放到同一个地方定义。
2.1、传播异常
__exit__方法只是向流程链上传播异常,这是通过返回False实现的,根本不需要与异常实例交互。考虑下面的上下文管理器:
class BubbleExceptions(): def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): if exc_val: print('Bubbling up exception :%s.'%exc_val) return False with BubbleExceptions() as f: print(5/5)
print(5/0)
#在上下文管理器中运行普通代码块(不抛出异常)将不会做什么特别的事情。
1.0
#该代码抛出异常时: Bubbling up exception :division by zero. Traceback (most recent call last): File "C:/Users/Administrator/Desktop/test/mysql_pool_test.py", line 125, in <module> print(5/0) ZeroDivisionError: division by zero
在此有两件值得注意的重要事情,第一个输出行(Bubbling up exception ...开头)由__exit__方法自身产生,它对应于__exit__方法第二行中的print语句。这意味着__exit__方法的确运行了且已完成,因为该方法返回了False,所以被首先发送给__exit__的异常只是被重新抛出了。
2.2、终止异常
如前所述,__exit__方法拥有的另一个选项就是终止它所接收的异常。下面的上下文管理器终止所有可能发送给__exit__方法的异常(切记使用)
class BubbleExceptions(): def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): if exc_val: print('Bubbling up exception :%s.'%exc_val) return True with BubbleExceptions() as f: print(5/5) print(5/0)
1.0
Bubbling up exception :division by zero.
代码的主要区别是__exit__方法返回的是True而不是False.
注意的三点:
1,回溯消失了,由于异常被__exit__方法处理了(终止),因此程序没有引发异常,继续执行。
2,没有返回任何值。
3,在print(5/0)后面的所有代码都不会再执行。
2.3、处理特定异常类
一个简单的异常处理函数__exit__可以仅检查异常是否是特定异常类的实例,执行任何必要的异常处理,并根据是否获得其他类型的异常返回True或False。
class BubbleExceptions(): def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): print('exc_type:',exc_type) if not exc_type: return True if issubclass(exc_type,ZeroDivisionError): print('Bubbling up exception :%s.'%exc_val) return True return False with BubbleExceptions() as f: print(5/5) print(5/0) print('xxxxxxxxxxxxx')
执行结果:
1.0 exc_type: <class 'ZeroDivisionError'> Bubbling up exception :division by zero.
2.4、不包括的子类
如何完成类或实例的检查也可以更加灵活。例如,假如想要捕获一个给定的异常类,但是不希望显示的捕获它的子类。也可以理解针对性的处理异常类:
把上文中的if issubclass(exc_type,ZeroDivisionError): 变更为if exc_type==ZeroDivisionError。
2.5、基于属性的异常处理(不做过多的解释了)
下一章介绍一个更加简单的语法:
python标准库@contextlib.contextmanager
Python标准库还提供了更加易用的上下文管理器工具模块contextlib,它是通过生成器实现的,我们不需要再创建类以及__enter__和__exit__这两个特俗的方法。