python 中 with 语句最常用在打开文件,处理文件的输入输出操作,至于为什么这么做,最笼统的解释就是释放资源。
上下文管理器
上下文管理器(context manager)是用来在 python 里面解决资源管理操作时造成的资源泄露问题。
一个很简单的例子:
- for x in range(10000000):
复制代码 这里打开了 10000000 文件,然而没有关闭他们,代码就会报错:
- OSError: [Errno 23] Too many open files in system: 'test.txt'
复制代码 这是因为同时打开太多文件,一直占用着资源,没有正确释放导致的问题。
python 就采用上下文管理器来解决这个问题,最典型的应用是 with 语句。
上面的代码就可以写成:
- for x in range(10000000):
复制代码- with open('test.txt', 'w') as f:
复制代码 每次打开这个文件,执行完 f.write(‘hello') 这个语句后就会自动关闭。
用 with 语句读写文件等同于下面的代码:
- f = open('test.txt', 'w')
复制代码 这里 try ... finally ... 是捕捉异常,无论是否出错都要执行 f.close() 语句。
不过这样的代码略显冗余,也会容易漏写,而且Python的内置file类型是支持上下文管理协议的,所以更倾向于用 with 语句。
上下文管理器的实现
要实现上下文管理器,必须实现两个方法 – 一个负责进入语句块的准备操作,另一个负责离开语句块的善后操作。也就是说Python 类包含两个特殊的方法,分别名为:__enter__ 以及 __exit__ 。
上下文管理器有两种方法实现。方法一:类实现 __enter__ 以及 __exit__ 方法,方法二:contextlib 模块装饰器和生成器实现。
1.通过类实现实现__enter__以及__exit__方法
- def __init__(self, name, method):
复制代码- print('calling __init__ method')
复制代码- self.file = open(name, method)
复制代码- print('calling __enter__ method')
复制代码- def __exit__(self, exc_type, exc_val, exc_tb):
复制代码- print('calling __exit__ method')
复制代码- if __name__ == '__main__':
复制代码- with File('text.txt', 'w') as f:
复制代码 具体原理是:with 语句初始化 File 类的时调用 __init__ 方法,打开了文件,暂存了 File 类的 __exit__ 方法,然后调用 File 类的 __enter__ 文件,并把返回值传递给 as 后的参数,with 语句执行完时调用之前暂存的 __exit__ 方法。
执行 with 语句出现异常时,会把 exception_type、exception_value 和 traceback 传入 __eixt__ 方法,如果要处理抛出的异常可以这么写:
- [/code][code] def __enter__(self):
复制代码- print('__enter__ called')
复制代码- def __exit__(self, exc_type, exc_value, exc_tb):
复制代码- print(f'exc_type: {exc_type}')
复制代码- print(f'exc_value: {exc_value}')
复制代码- print(f'exc_traceback: {exc_tb}')
复制代码- print('exception handled')
复制代码- # 正常返回需要添加 return True,否则仍会抛出异常
复制代码- raise Exception('exception raised').with_traceback(None)
复制代码- exc_value: exception raised
复制代码 2.通过 contextlib 模块装饰器和生成器实现
- from contextlib import contextmanager
复制代码- [/code][code]@contextmanager
复制代码- def read_file(name, mode):
复制代码- if __name__ == '__main__':
复制代码- with read_file('text.txt', 'r') as f:
复制代码- for line in f.readlines():
复制代码 yield之前的代码由 __enter__ 方法执行,yield之后的代码由 __exit__ 方法执行。本质上还是 __enter__ 和 __exit__ 方法。
函数 read_file() 是一个生成器,执行 with 语句时,会打开文件,执行到 yiled 时会把对象返回给 as 后的参数,with 语句执行完后,会继续执行 yiled 之后的语句。
以上就是本篇文章全部。
结合自己的理解以及专栏学习笔记所写。
|
|