python 中的上下文管理器和 with 语句

论坛 期权论坛 期权     
笙歌似海   2019-7-27 14:24   2550   0
python 中 with 语句最常用在打开文件,处理文件的输入输出操作,至于为什么这么做,最笼统的解释就是释放资源。

上下文管理器
上下文管理器(context manager)是用来在 python 里面解决资源管理操作时造成的资源泄露问题。

一个很简单的例子:
  1. for x in range(10000000):
复制代码
  1.     f = open('test.txt','w')
复制代码
  1.     f.write('hello')
复制代码
这里打开了 10000000 文件,然而没有关闭他们,代码就会报错:
  1. OSError: [Errno 23] Too many open files in system: 'test.txt'
复制代码
这是因为同时打开太多文件,一直占用着资源,没有正确释放导致的问题。

python 就采用上下文管理器来解决这个问题,最典型的应用是 with 语句。

上面的代码就可以写成:

  1. for x in range(10000000):
复制代码
  1.     with open('test.txt', 'w') as f:
复制代码
  1.         f.write('hello')
复制代码
每次打开这个文件,执行完 f.write(‘hello') 这个语句后就会自动关闭。

用 with 语句读写文件等同于下面的代码:
  1. for x in range(1000000):
复制代码
  1.     f = open('test.txt', 'w')
复制代码
  1.         try:
复制代码
  1.             f.write('hello')
复制代码
  1.         finally:
复制代码
  1.             f.close()
复制代码
这里 try ... finally ... 是捕捉异常,无论是否出错都要执行 f.close() 语句。

不过这样的代码略显冗余,也会容易漏写,而且Python的内置file类型是支持上下文管理协议的,所以更倾向于用 with 语句。

上下文管理器的实现

要实现上下文管理器,必须实现两个方法 – 一个负责进入语句块的准备操作,另一个负责离开语句块的善后操作。也就是说Python 类包含两个特殊的方法,分别名为:__enter__ 以及 __exit__ 。
上下文管理器有两种方法实现。方法一:类实现 __enter__ 以及 __exit__ 方法,方法二:contextlib 模块装饰器和生成器实现。


1.通过类实现实现__enter__以及__exit__方法
  1. class File:
复制代码
  1.    
复制代码
  1.     # 初始化方法,生成类对象时调用
复制代码
  1.     def __init__(self, name, method):
复制代码
  1.         print('calling __init__ method')
复制代码
  1.         self.file = open(name, method)
复制代码
  1.    
复制代码
  1.     # 返回对象
复制代码
  1.     def __enter__(self):
复制代码
  1.         print('calling __enter__ method')
复制代码
  1.         return self.file
复制代码
  1. [/code][code]    # 执行结束后调用
复制代码
  1.     def __exit__(self, exc_type, exc_val, exc_tb):
复制代码
  1.         print('calling __exit__ method')
复制代码
  1.         self.file.close()
复制代码
  1.         
复制代码
  1. if __name__ == '__main__':
复制代码
  1.     with File('text.txt', 'w') as f:
复制代码
  1.         print('write')
复制代码
  1.         f.write('hello')
复制代码
  1. [/code][code]# 输出
复制代码
  1. calling __init__ method
复制代码
  1. calling __enter__ method
复制代码
  1. write
复制代码
  1. calling __exit__ method
复制代码
具体原理是:with 语句初始化 File 类的时调用 __init__ 方法,打开了文件,暂存了 File 类的 __exit__ 方法,然后调用 File 类的 __enter__ 文件,并把返回值传递给 as 后的参数,with 语句执行完时调用之前暂存的 __exit__ 方法。
执行 with 语句出现异常时,会把 exception_type、exception_value 和 traceback 传入 __eixt__ 方法,如果要处理抛出的异常可以这么写:
  1. class Foo:
复制代码
  1.     def __init__(self):
复制代码
  1.         print('__init__ called')
复制代码
  1. [/code][code]    def __enter__(self):
复制代码
  1.         print('__enter__ called')
复制代码
  1.         return self
复制代码
  1.    
复制代码
  1.     def __exit__(self, exc_type, exc_value, exc_tb):
复制代码
  1.         print('__exit__ called')
复制代码
  1.         if exc_type:
复制代码
  1.             print(f'exc_type: {exc_type}')
复制代码
  1.             print(f'exc_value: {exc_value}')
复制代码
  1.             print(f'exc_traceback: {exc_tb}')
复制代码
  1.             print('exception handled')
复制代码
  1.         # 正常返回需要添加 return True,否则仍会抛出异常
复制代码
  1.         return True
复制代码
  1.    
复制代码
  1. with Foo() as obj:
复制代码
  1.     raise Exception('exception raised').with_traceback(None)
复制代码
  1. [/code][code]# 输出
复制代码
  1. __init__ called
复制代码
  1. __enter__ called
复制代码
  1. __exit__ called
复制代码
  1. exc_type:
复制代码
  1. exc_value: exception raised
复制代码
  1. exc_traceback:
复制代码
  1. exception handled
复制代码
2.通过 contextlib 模块装饰器和生成器实现
  1. from contextlib import contextmanager
复制代码
  1. [/code][code]@contextmanager
复制代码
  1. def read_file(name, mode):
复制代码
  1.     f = open(name, mode)
复制代码
  1.     try:
复制代码
  1.         yield f
复制代码
  1.     except Exception as e:
复制代码
  1.         print(e)
复制代码
  1.     finally:
复制代码
  1.         f.close()
复制代码
  1. [/code][code]
复制代码
  1. if __name__ == '__main__':
复制代码
  1.     with read_file('text.txt', 'r') as f:
复制代码
  1.         for line in f.readlines():
复制代码
  1.             print(line)
复制代码
  1.             
复制代码
  1. # 输出
复制代码
  1. hello
复制代码
yield之前的代码由 __enter__ 方法执行,yield之后的代码由 __exit__ 方法执行。本质上还是 __enter__ 和 __exit__ 方法。

函数 read_file() 是一个生成器,执行 with 语句时,会打开文件,执行到 yiled 时会把对象返回给 as 后的参数,with 语句执行完后,会继续执行 yiled 之后的语句。

以上就是本篇文章全部。
结合自己的理解以及专栏学习笔记所写。


分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:5
帖子:1
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP