Python学习笔记 with和上下文管理器

目录

  with语句(一)

  上下文管理器

  with语句(二)

with语句(一)

  我们都知道,平时在对文件里的数据进行进行读取或写入时都要先打开文件,完事之后再关闭文件。但有些客官记性不好(比如我),总是打开之后就忘了关闭。忘了关闭就会出事情,比如可能会造成文件数据丢失什么的,就会很危险!不过聪明的Python语言设计者早就预料到了有这种危险的可能,所以他们就发明了with语句。

以上纯属胡扯,下面正文开始

  with 语句常用于对资源进行访问的场合,它能确保程序在对资源的使用之后执行必要的清理工作,释放资源(比如文件和数据库的自动关闭、线程中锁的自动获取和释放等),而不管在使用过程中是否发生异常。
  那么对文件进行操作的代码就可以写成这样

with open('file.txt') as myfile:
    myfile.write('Y')

  利用这种写法,我们就不需要担心文件有没有关闭的问题了,因为 with 语句会自动帮我们做好这一切。但其实我们不是很明白,with 语句是怎么做到这一切的,难道它会魔法?
  要想搞清楚上面 with 语句的魔法,我们就得先搞清楚with语句中的 open() ———一个上下文管理器

上下文管理器

先介绍俩术语
  上下文管理协议:本协议指出,创建的任何类都必须至少定义两种魔法方法,即 __ enter__ 和 __ exit__ 方法。
  上下文管理器:就是遵循上下文管理协议的类(class)。这个类定义了 __ enter__ 和 __ exit__ 方法。

再介绍俩魔法方法
  __ enter__ 方法:该方法定义当使用 with 语句时的初始化行为,且 __ enter__ 的返回值(如果有的话)被 with 语句的
       目标或者 as 后的名字绑定。
  __ exit__ 方法:该方法定义 当一个代码组被执行或者终止后上下文管理器的行为。

  哦,原来如此,open() 是一个类,而这个类中的 __ enter__ 和 __ exit__ 方法定义了 with 语句代码组开头和末尾的行为。那__ enter__ 和 __ exit__ 方法中肯定定义了打开文件和关闭文件行为,所以我们才不用手动打开和关闭文件了。啧啧,怪不得叫上下文管理器呢,原来是把 with 语句的开头(上文)和结尾(下文)都规定好了。

  那我自己也能写一个上下文管理器喽,只要我定义__ enter__ 和 __ exit__ 方法

class MyContext():
    def __enter__(self):
        print('enter:呼叫with的代码组,呼叫with的代码组')
        print('enter:这里是__enter__方法,我是你们的先遣部队,请代码组跟在我后头执行')
        print('enter:over')
        return '我是enter的返回值,with会用as给我起个别名,就是跟在as后面那个'
    def __exit__(self, exc_type, exc_val, exc_tb):
        print('exit:你们先上,我垫后')
with MyContext() as alias:
    print(alias)
    print('这里是with的代码组,我们正跟在先遣队之后')
'''
enter:呼叫with的代码组,呼叫with的代码组
enter:这里是__enter__方法,我是你们的先遣部队,请代码组跟在我后头执行
enter:over
我是enter的返回值,with会用as给我起个别名,就是跟在as后面那个
这里是with的代码组,我们正跟在先遣队之后
exit:你们先上,我垫后
'''

  上面是写的一个上下文管理器,并用 with 语句对它进行了实验,结果很成功。

  更常见的,当我们写一个上下文管理器时,除了两个必要的魔法方法,还会用的其他的方法,比如 __ init__ 方法(如果你想像open一样往管理器传入参数)等。我们常让 __ enter__ 方法做一些资源初始化的工作,让 __ exit__ 方法做一些清理和对异常的处理工作。

  等一下,上下文管理器就是定义开头和结尾的行为,那么我定义一个只有一个 yield 的生成器函数(点进去了解生成器是什么),然后分别在代码的开头和结尾两次调用,岂不是也可以达到上下文管理器同样的效果?

  没错,确实可以达到相同的效果。但是Python为了免除你两次调用的麻烦,在 contextlib 模块中提供了一个名为 contextmanager 的装饰器。当生成器函数被该装饰器装饰以后,会返回一个上下文管理器,该管理器的 __ enter__ 和 __ exit__ 方法由 contextmanager 负责提供。需要注意的是被装饰的生成器函数只能产生一个值,即只能有一个 yield 表达式,否则就会出现异常。产生的值会像 __ enter__ 的返回值一样,与 with 语句中 as 后面的变量发生绑定关系。例如下

from contextlib import contextmanager
# 导入装饰器
@contextmanager
def myContext():
    print('这里是生成器myContext的前半部分')
    print('我相当于上下文管理器中的__enter__方法')
    print('eover')
    yield '我相当于enter的返回值,with会用as给我起个别名,就是跟在as后面那个'
    print('我相当于上下文管理器中的__exit__方法')
with myContext() as alias:
    print(alias)
    print('这里是with的代码组,我们正跟在先遣队之后')
'''
这里是生成器myContext的前半部分
我相当于上下文管理器中的__enter__方法
eover
我相当于enter的返回值,with会用as给我起个别名,就是跟在as后面那个
这里是with的代码组,我们正跟在先遣队之后
我相当于上下文管理器中的__exit__方法
'''

  其实 contextlib 模块还提供了一个叫 nested 的函数和一个叫 closeing的上下文管理器

with语句(二)

  我们通过下面那个一个类似 open() 的上下文管理器捋一遍 with 语句,再说说 __ exit__ 方法的那一大串参数都是什么东西。

class MyOpen():
    def __init__(self, file_name, way='r'):
        # 默认文件以只读的方式打开
        self.file_name = file_name
        self.way = way
    def __enter__(self):
        self.handle = open(self.file_name, self.way)
        # 这里也可能产生异常,比如文件不存在或没有权限打开文件
        # 所以也应该写一下产生异常的处理 用 try/except
        return self.handle
    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_val != None:
            print('文件操作过程中产生了异常:',exc_type)
            # 产生异常就输出 exc_type
        self.handle.close()
with MyOpen('上下文管理.txt') as myfile:
    data = myfile.readline()
    print(data)

  上面 with 语句的执行过程是:先执行上下文管理器的__ init__ 方法,然后执行 __ enter__ 方法,再然后执行 with 语句的代码组,最后执行上下文管理器的 __ exit__ 方法。

  上下文管理器 MyOpen() 可以说是个阉割版的 open 吧,它实现了文件的打开和关闭操作,还有一个在 __ exit__ 方法中对异常的捕获和处理。

  __ exit__ 方法可以自动捕获在它执行之前产生的异常,并把异常的类型,异常的值和异常回溯跟踪对象分别赋予它的后三个参数。其中 exc_type 是异常的类型, exc_val 是异常的值,exc_tb 是异常回溯跟踪对象。当该方法没有捕获异常时(之前没有产生异常),这三个参数都默认为 None 。所以可以根据这三个参数的值是否为空来判断有没有异常产生。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值