# With上下文管理器和魔术方法

# 概述

对于正确的处理涉及到异常的资源管理时,需要使用 try/finally 代码结构,这样的结构一多会导致整体代码结构 很臃肿繁琐,不易读、不美观,因此在 Python2.6 版本推出 with 关键字。

with as 语句是 Pyhton 提供的一种简化语法,适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的清理操作,释放资源。

对于系统资源如文件、数据库连接、socket 而言,应用程序打开这些资源并执行完业务逻辑之后,必须做的一件事就是要关闭(释放)该资源。

比如 Python 程序打开一个文件,往文件中写内容,写完之后,就要关闭该文件,如果不关闭会出现什么情况呢?极端情况下会出现 Too many open files 的错误,因为系统允许你打开的最大文件数量是有限的。

同样,对于数据库,如果连接数过多而没有及时关闭的话,就可能会出现 Can not connect to MySQL server Too many connections,因为数据库连接是一种非常昂贵的资源,不可能无限制的被创建。

并且如下面的代码片段,如果代码在执行close之前报错了,那么资源实际上是没有释放的。

file = "test.txt"

def fun1():
    """
    普通版
    """
    f = open(file, "w")
    f.write("hello python")
    f.close

Python 提供了 with 语法用于简化资源操作的后续清除操作,是 try/finally 的替代方法,实现原理建立在上下文管理器之上。此外,Python 还提供了一个 contextmanager 装饰器,更进一步简化上下文管理器的实现方式。基于类和基于 contextmanager 的上下文管理器,这两者在功能上是一致的。只不过,基于类的上下文管理器 更加灵活,适用于大型的系统开发,而基于 contextmanager 的上下文管理器 更加方便、简洁,适用于中小型程序。

# 上下文管理器

任何类实现了 __enter__()​ 和 __exit__()​ 方法的对象都可称之为上下文管理器。

上下文管理器对象可以使用 with​​ 关键字。

但是注意,__enter__()​方法有时会返回self​-即as​后的赋值操作-即as​是可选的,有时不会,但是__exit__()​始终在上下文管理器上调用,而不是在__enter__()​方法返回的对象上调用。

with open('file.txt', 'r') as f:
    content = f.read()
    print(content)

# 基于类的上下文管理器

  • 当我们用类来创建上下文管理器时,必须保证这个类包括方法__enter__()​和方法__exit__()​。

    • 其中,方法__enter__()​返回需要被管理的资源,方法__exit__()​里通常会存在一些释放、清理资源的操作
    • 方法__exit__()​中的参数exc_type, exc_val, exc_tb​”,分别表示 exception_type、exception_value 和 traceback​。当我们执行含有上下文管理器的 with 语句时,如果有异常抛出,异常的信息就会包含在这三个变量中,传入方法__exit__()​。
  • 基于类的上下文管理器更加 flexible,适用于大型的系统开发;


class FileManager:
    def __init__(self, name, mode):
        print('calling __init__ method')
        self.name = name
        self.mode = mode 
        self.file = None
      
    def __enter__(self):
        print('calling __enter__ method')
        self.file = open(self.name, self.mode)
        return self.file


    def __exit__(self, exc_type, exc_val, exc_tb):
        print('calling __exit__ method')
        if self.file:
            self.file.close()
          
with FileManager('test.txt', 'w') as f:
    print('ready to write to file')
    f.write('hello world')
  
## 输出
calling __init__ method
calling __enter__ method
ready to write to file
calling __exit__ method

# 基于生成器的上下文管理器

  • 可以使用装饰器 contextlib.contextmanager,来定义自己所需的基于生成器的上下文管理器,用以支持 with 语句。
  • 使用基于生成器的上下文管理器时,不再用定义“enter()”和“exit()”方法,但需要加上装饰器 @contextmanager
  • 基于生成器的上下文管理器更加方便、简洁,适用于中小型程序。

from contextlib import contextmanager

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