资源管理
在任何编程语言中,对于资源的使用诸如文件操作或数据库连接等非常普遍。 但是这些资源是有限的,因此我们要确保使用这些资源后及时释放他们, 否则将导致资源泄漏,甚至会使系统变慢或崩溃。 在Python中,可以通过使用上下文管理器来实现对于资源的分配和及时释放,这有助于对资源的正确处理。 执行文件操作的最常见方式是使用with关键字,假设你有两个相关的操作要成对执行,并且这两个相关操作之间还有一段代码,我们就可以使用上下文管理器实现这个目的。 基本语法格式为:
with EXPR as VAR:
BLOCK
以文件操作为例,当我们打开一个文件,就会消耗一个文件描述符.文件描述符属于有限的资源,因此一个进程一次只能打开有一定数量的文件,如果们打开了太多文件而且没有及时关闭就会造成文件描述符泄露.下面用代码来解释这个问题
>>> file_descriptors = [] >>> for x in range(100000): ... file_descriptors.append(open('test.txt', 'w')) ... Traceback (most recent call last): File "<stdin>", line 2, in <module> OSError: [Errno 24] Too many open files: 'test.txt'
使用上写文管理器进行资源管理
如果代码块抛出异常,或者代码块包含具有多个返回路径的复杂算法,则在每个位置逐一关闭文件将变得很麻烦.在使用其他语言时,使用try-except-finally可以确保即使有异常也可以在使用后关闭文件资源.Python提供了一种更简单的方法来管理资源:上下文管理器. 使用with关键字例如:
with open("test.txt", 'w') as f: f.write('Hello World!')
这段代码执行打开文件,写进数据,关闭文件操作.如果在写进数据的时候抛出错误,上下文管理器会尝试解决这个错误, 它与下边这段代码的作用是一样的.
file = open('test.txt', 'w') try: file.write('Hello World!') finally: file.close()
比较这两段代码我们就会发现使用with使代码更加精简,但使用with的最主要好处是他能够确保关闭文件,且不需要考虑嵌套块退出的方式.
上下文管理器的常见用例是占用和释放资源以及关闭打开的文件.接下来我们尝试自己实现一个上下文管理器,从而对他有一个更好的理解
实现上下文管理器
上下文管理器可以使用类或函数(带有装饰器)来编写,当我们用类实现上下文管理器时,要确保类中含有 __enter__() 和 __exit__()方法. __enter__() 用来返回要被使用的资源,__exit__() 执行清除操作.接下来我们用两个例子解释如何用类创建上下文管理器.
>>> class ContextManager(): ... def __init__(self): ... print('init method called') ... ... def __enter__(self): ... print('enter method called') ... return self ... ... def __exit__(self, exc_type, exc_value, exc_traceback): ... print('exit method called') ... >>> with ContextManager() as manager: ... print('with statement block') ... init method called enter method called with statement block exit method called
从输出结果中我们不难看出这段代码执行顺序如下
__init__() __enter__() code inside the with block __exit__()
接下来以文件操作为例来解释如何用类来创建上下文管理器
>>> class FileManager(): ... def __init__(self, filename, mode): ... self.filename = filename ... self.mode = mode ... self.file = None ... ... def __enter__(self): ... self.file = open(self.filename, self.mode) ... return self.file ... ... def __exit__(self, exc_type, exc_value, exc_traceback): ... self.file.close() ... >>> with FileManager('demo.txt', 'w') as f: ... f.write('Hello World!') ... >>> print(f.closed) True >>>
执行上述代码,实际运行过程如下
- with语句把
__exit__方法
存储在 FileManager中. - 调用 FileManager类中的__enter__方法
- __enter__方法以写模式打开文件并返回这个FileManager对象
- 将__enter__方法的返回值赋值给变量f
- 利用.write()在文件中写入"Hello World"
- with调用__exit__方法
- __exit__关闭文件
异常处理
__exit__方法有三个参数:exc_type, exc_val, exc_tb。如果with下代码块发生异常并退出,那么分别对应异常的type、value 和 traceback。否则三个参数全为None。具体来说,如果在第四到第六步有异常抛出,Python会把异常的type、value 和 traceback传给__exit__方法.之后由__exit__方法决定如何关闭文件以及其他后续操作.实际运行过程如下
- with把异常的type、value 和 traceback传给__exit__方法
- __exit__处理异常
- 如果__exit__返回True则说明异常被成功处理
- 如果__exit__返回其他值,则异常会被with抛出
在我们自定义的代码中__exit__方法返回的是None,因此如果我们尝试访问一个不存在的函数,with语句则会抛出如下异常
>>> with FileManager('demo.txt', 'w') as f: ... f.undefined_function('haha') ... Traceback (most recent call last): File "<stdin>", line 2, in <module> AttributeError: '_io.TextIOWrapper' object has no attribute 'undefined_function' >>>
修改__exit__方法来处理异常
>>> class FileManager(): ... def __init__(self, filename, mode): ... self.filename = filename ... self.mode = mode ... self.file = None ... ... def __enter__(self): ... self.file = open(self.filename, self.mode) ... return self.file ... ... def __exit__(self, exc_type, exc_value, exc_traceback): ... print("Exception has been handled") ... self.file.close() ... return True ... >>> with FileManager('demo.txt', 'w') as f: ... f.undefined_function() ... Exception has been handled >>>
修改后的方法返回True, 表示这个异常已经被(以某种方法)处理了,with也不会再抛出异常