Python with as上下文管理器

with as用法详解

在任何一门编程语言中,文件的输入输出以及数据库的断开连接等,都是很常见的资源管理操作。但资源都是有限的,在写程序时,必须保证这些资源在使用过后得到释放,不然就容易造成资源泄露,轻者使得系统处理缓慢,严重时会使系统崩溃

在文件操作时打开的文件最后一定要关闭,否则会使程序的运行造成意想不到的隐患。但是即使close()做好了关闭文件的操作,如果在打开文件或文件过程中抛出了异常,还是无法及时关闭文件

为了更好的避免此类问题,不同的编程语言都引入了不同的机制。在python中,对应的解决方式是使用with as语句操作上下文管理器,它能够帮助我们自动分配并且释放资源

  • 简单理解,同时包含__enter()__和__exit()__方法的对象就是上下文管理器。常见构建上下文管理器的方式有两种,分别是基于类实现和基于生成器实现

例如,使用with as操作已经打开的文件对象(本身就是上下文管理器),无论期间是否抛出异常,都能保证with as语句执行完毕后自动关闭已经打开的文件

首先学习如何使用 with as 语句。with as 语句的基本语法格式为:

with 表达式 [as target]:
    代码块

此格式中,用[]括起来的部分可以使用,也可以忽略。其中,target参数用于指定一个变量,该语句会将expression指定的结果保存到该变量中。with as 语句中的代码如果不想执行任何语句可以直接用pass语句代替

举个例子,假设有一个a.txt文件,其存储内容如下

aaaaaaaaaaaaa

在和 a.txt 同级目录下,创建一个 .py 文件,并编写如下代码:

with open('a.txt', 'a') as f:
    f.write("\nPython")

结果
aaaaaaaaaaaaa
python

可以看到,通过使用 with as 语句,即便最终没有关闭文件,修改文件内容的操作也能成功。

with as底层原理详解

在介绍with as语句时讲到,该语句操作的对象必须是上下文管理器。那么,到底什么是上下文管理器呢?

简单的理解,同时包含__enter__()和__exit()__()方法的对象就是上下文管理器。也就是说上下文管理器必须实现如下两个方法:

  1. __enter(self):进入上下文管理器自动调用的方法,该方法会在with as 代码执行之前执行。如果with语句有as子句,那么该方法的返回值会被赋值给as子句后面的变量;该方法可以返回多个值,因此在as子句后面也可以指定多个变量(多个变量必须由"()"括起来组成元组)
  2. __exit(self,exc_type, exc_value, exc_traceback):退出上下问管理器自动调用的方法。该方法会在with as代码块执行之后执行。如果with as代码块成功执行结束,程序自动调用该方法,调用该方法的三个参数都是None;如果with as代码因异常而终止,程序也自动调用该方法,使用sys.exc_info得到得到的异常信息将作为调用该方法的参数

当with as操作上下文管理器时,就会在执行语句体之前先执行上下文管理器的__enter__方法,然后再执行语句体,最后执行__exit__()方法。

构建上下文管理器,常见的有 2 种方式:基于类实现和基于生成器实现。

基于类的上下文管理器
通过上面的介绍不难发现,只要一个类实现了__enter__()和__exit__()这两个方法,程序就可以使用with as语句来管理它,通过__exit__()方法的参数,即可判断出with代码块执行时是否遇到了异常。

下面我们自定义一个实现上下文管理协议的类,并尝试用 with as 语句来管理它:

class FkResource:
    def __init__(self, tag):
        self.tag = tag
        print('构造器,初始化资源: %s' % tag)
    # 定义__enter__方法,with体之前的执行的方法
    def __enter__(self):
        print('[__enter__ %s]: ' % self.tag)
        # 该返回值将作为as子句中变量的值
        return 'fkit'  # 可以返回任意类型的值
    # 定义__exit__方法,with体之后的执行的方法
    def __exit__(self, exc_type, exc_value, exc_traceback):
        print('[__exit__ %s]: ' % self.tag)
        # exc_traceback为None,代表没有异常
        if exc_traceback is None:
            print('没有异常时关闭资源')
        else:
            print('遇到异常时关闭资源')
            return False   # 可以省略,默认返回None也被看做是False
with FkResource('孙悟空') as dr:
    print(dr)
    print('[with代码块] 没有异常')
print('------------------------------')
with FkResource('白骨精'):
    print('[with代码块] 异常之前的代码')
    raise Exception
    print('[with代码块] ~~~~~~~~异常之后的代码')

结果
构造器,初始化资源: 孙悟空
Traceback (most recent call last):
[__enter__ 孙悟空]: 
fkit
[with代码块] 没有异常
[__exit__ 孙悟空]: 
没有异常时关闭资源
------------------------------
构造器,初始化资源: 白骨精
[__enter__ 白骨精]: 
[with代码块] 异常之前的代码
[__exit__ 白骨精]: 
遇到异常时关闭资源
    raise Exception
Exception

上面程序定义了一个 FkResource 类,并包含了 enter() 和 exit() 两个方法,因此该类的对象可以被 with as 语句管理。

此外,程序中两次使用 with as 语句管理 FkResource 对象。第一次代码块没有出现异常,第二次代码块出现了异常。从上面的输出结果来看,使用 with as 语句管理资源,无论代码块是否有异常,程序总可以自动执行 exit() 方法。

基于生成器的上下文管理器
除了基于类的上下文管理器,它还可以基于生成器实现。接下来先看一个例子。比如,我们可以使用装饰器 contextlib.contextmanager,来定义自己所需的基于生成器的上下文管理器,用以支持 with as 语句:

from contextlib import contextmanager
@contextmanager
def file_manager(name, mode):
    try:
        f = open(name, mode)
        yield f
    finally:
        f.close()
       
with file_manager('a.txt', 'w') as f:
    f.write('hello world')

这段代码中,函数 file_manager() 就是一个生成器,当我们执行 with as 语句时,便会打开文件,并返回文件对象 f;当 with 语句执行完后,finally 中的关闭文件操作便会执行。另外可以看到,使用基于生成器的上下文管理器时,不再用定义 enter() 和 exit() 方法,但需要加上装饰器 @contextmanager,这一点新手很容易疏忽。

需要强调的是,基于类的上下文管理器和基于生成器的上下文管理器,这两者在功能上是一致的。只不过,基于类的上下文管理器更加灵活,适用于大型的系统开发,而基于生成器的上下文管理器更加方便、简洁,适用于中小型程序。但是,无论使用哪一种,不用忘记在方法“exit()”或者是 finally 块中释放资源,这一点尤其重要。

本文转载自添加链接描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值