上下文管理器-基本概念及其实现

305 阅读5分钟

概要 本文是上下文管理器专题第一篇,主要简单介绍上下文及上下文管理器的基本概念,分析with操作符的使用及其优缺点,最后还介绍了如何实现自定义上下文管理器

什么是上下文 程序中所谓的上下文件就是指程序所执行的环境状态,或者说程序运行的情景

关于return self

查看第四个代码块

上下文管理器定义 写代码时,我们希望把一些操作放到一个代码块中,这样在代码块中执行时就可以保持在某种运行状态,而当离开该代码块时就执行另一个操作,结束当前状态;所以,简单来说,上下文管理器的目的就是规定对象的使用范围,如果超出范围就采取“处理”,这一功能是在Python3.5之后引进的,它的优势在于可以使得你的代码更具可读性,且不容易出错。

常见的上下文管理器:with操作符 最常见的上下文管理器就是with语句了,python提供了with语句语法,来构建对资源的自动创建与自动释放,允许你在有需要的时候,精确地分配和释放资源

使用方法: 首先我们来看一段未使用with语句的代码,看看它在运行过程中会出现什么问题?

#when without using context manager
f = open("hl.txt", "w")
print(f.closed)  #运行到这里,会打印出False,因为没有关闭
f.write("Hello,context manager!")
f.close()
print(f.closed)  #这里才会打印出True,表示文件关闭

表面上看似这段代码也并没什么大的问题,但如果这段代码根本执行不到f.close()这一句就会出现问题,假设我们在写入的过程中,磁盘满了,代码就会在写入的那一句出现异常,这样就永远执行不到f.close()这一句,文件就永远不会被关闭,当然也可以用try…finally语句,如下所示:

#when using try…finally
try:
    f = open("hl.txt", "w")
    print(f.closed)  #运行到这里,会打印出False,因为没有关闭
    f.write("Hello,context manager!")
finally:
    f.close()
print(f.closed)  #这里才会打印出True,表示文件关闭

但是同样存在问题,当我们执行的不是简单的写入操作,而是其他更复杂的操作,如拷贝,同时读和写,try…finally语句根本无法保证代码的美感。这时,with就派上用场了。

下面,让我们来看一下使用了with的情况

#when using context manager
with open("hl.txt","w") as f:
    print(f.closed)  #False
    f.write("Hello,context manager!")
print(f.closed)  #True 无需执行f.close()

上面代码中,可以看到一个代码块:with…as… 其中,with关键词总是伴随着上下文管理器出现,as关键词将open(“hl.txt”,”w”)赋给了新的对象f,使得在该代码块中可以对f执行任意操作,代码相当简洁。

使用with的优点: 避免了琐碎操作:通过使用with,许多样板代码可以被消掉 避免了遗忘步骤:因此不用关注嵌套代码如何退出,又能确保我们的文件会被关闭

实现自定义上下文管理器 实现了上下文协议的函数/对象即为上下文管理器,上下文管理协议则是由enter和exit构成 要实现上下文管理器可以有两种方式,一种为基于类的实现,一种为基于生成器的实现

基于类的实现: 基于类的实现需要实现两个方法:enter()和_exit_() enter():负责进入代码块的准备工作,进入前被调用; exit():负责离开代码块的善后工作,离开后被调用;

class File(object):
    def __init__(self, file_name, method):
        self.file_obj = open(file_name, method)
    def __enter__(self):
        return self.file_obj
    def __exit__(self, type, value, traceback):
        self.file_obj.close()

任何定义了_enter_()和_exit_()方法的对象都可以用于上下文管理器,通过定义enter和exit方法,我们可以在with语句里使用它

with File('demo.txt', 'w') as opened_file: opened_file.write('Hola!') 我们的exit函数接受三个参数。这些参数对于每个上下文管理器类中的exit方法都是必须的。我们来看看在底层都发生了什么。

  1. with语句先调用File类的__enter__方法

  2. __enter__方法打开文件并返回给with语句

  3. 打开的文件句柄被传递给opened_file参数

  4. 我们使用.write()来写文件

  5. with语句调用__exit__方法关闭文件。

处理异常 如果发生异常,Python会将异常的type,value和traceback传递给exit方法 尝试访问文件对象的一个不支持的方法

with File('demo.txt', 'w') as opened_file:
    opened_file.undefined_function('Hola!')

exit方法返回的是None(如果没有return语句那么方法会返回None)。因此,with语句抛出了那个异常。

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
AttributeError: 'file' object has no attribute 'undefined_function'

当异常发生时,with语句会采取下面4步。

  1. 它把异常的type,value和traceback传递给exit方法。
  2. 它让exit方法来处理异常
  3. 如果exit返回的是True,那么这个异常就被优雅地处理了。
  4. 如果exit返回的是True以外的任何东西,那么这个异常将被with语句抛出

在exit方法中处理异常:

class File(object):
    def __init__(self, file_name, method):
        self.file_obj = open(file_name, method)
    def __enter__(self):
        return self.file_obj
    def __exit__(self, type, value, traceback):
        print("Exception has been handled")
        self.file_obj.close()
        return True

with File('demo.txt', 'w') as opened_file:
    opened_file.undefined_function()

exit方法返回了True,因此没有异常会被with语句抛出

基于生成器的实现: 我们还可以用装饰器(decorators)和生成器(generators)来实现上下文管理器 Python有个contextlib模块专门用于这个目的。我们可以使用一个生成器函数来实现一个上下文管理器,而不是使用一个类。

from contextlib import contextmanager

@contextmanager
def file_open(path):
    try:
        f_obj = open(path,"w")
        yield f_obj
    except OSError:
        print("We had an error!")
    finally:
        print("Closing file")
        f_obj.close()

if __name__ == "__main__":
    with file_open("test/test.txt") as fobj:
        fobj.write("Testing context managers")

在这里,我们从contextlib模块中引入contextmanager,然后装饰我们所定义的file_open函数。这就允许我们使用Python的with语句来调用file_open函数。在函数中,我们打开文件,然后通过yield,将其传递出去,最终主调函数可以使用它。

一旦with语句结束,控制就会返回给file_open函数,它继续执行yield语句后面的代码。这个最终会执行finally语句--关闭文件。如果我们在打开文件时遇到了OSError错误,它就会被捕获,最终finally语句依然会关闭文件句柄。

相关链接: www.cnblogs.com/Jeffding/p/…