上下文管理器的使用

通常在涉及确保某种资源以一种期望的方式被初始化和反初始化,或者尽力去避免重复时使用上下文管理器。

编写管理器常见的几个环境场所:

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__这两个特俗的方法。

 

posted @ 2021-11-16 15:36  乔小生1221  阅读(80)  评论(0编辑  收藏  举报