本文最后更新于 419 天前,其中的信息可能已经过时,如有错误请发送邮件到 wuxianglongblog@163.com
读写文件时,如果一个文件被打开,且未被正常关闭,可能会出现一些意想不到的结果。
Python 提供了上下文管理器的机制来解决这个问题,它通常与关键字 with 一起使用。对于上面的例子,用 with 语句调用的方式为:
| with <expression>: |
| <statements> |
| with open('my_file', 'w') as fp: |
| |
| data = fp.write("Hello world") |
等价于:
| fp = open('my_file', 'w') |
| try: |
| |
| data = fp.write("Hello world") |
| finally: |
| fp.close() |
处理文件,线程,数据库,网络编程等等资源的时候,经常需要使用上面这样的代码形式,以确保资源的正常使用和释放。
上下文管理器需要 <expression>
中的结果能够支持.__enter__()
和.__exit__()
方法:
| fp = open('my_file', 'w') |
可以定义一个支持上述方法的自定义上下文管理器:
| class TestManager(object): |
| |
| def __enter__(self): |
| print("Entering") |
| |
| def __exit__(self, exc_type, exc_value, traceback): |
| print("Exiting") |
| with TestManager(): |
| print("Hello") |
如果在执行过程中抛出了异常,.exit() 方法会先被执行,然后抛出异常:
| with TestManager(): |
| print(1 / 0) |
| Entering |
| Exiting |
| |
| |
| |
| ZeroDivisionError Traceback (most recent call last) |
| |
| Input In [8], in () | |
| 1 with TestManager(): |
| |
| |
| ZeroDivisionError: division by zero |
在读文件的例子中,在 <statements>
中使用文件对象时使用了 as 关键字的形式,将 open()
函数返回的文件对象赋给了 f。事实上,as 关键字只是将上下文管理器.__enter__()
方法的返回值赋给了 f,而文件对象的.__enter__()
方法的返回值刚好是它本身:
| fp = open('my_file', 'w') |
| |
| fp.__enter__() is fp |
一个通常的做法是将.__enter__()
方法的返回值设为这个上下文管理器对象本身,也可以是其他值:
| class TestManager(object): |
| |
| def __enter__(self): |
| print("Entering") |
| return "Hello" |
| |
| def __exit__(self, exc_type, exc_value, traceback): |
| print("Exiting") |
| with TestManager() as f: |
| print(f) |
__exit__()
方法接受的参数中有一些错误信息,如果没有错误,这些参数为 None
,如果有错误,可以在这个方法里对一些错误进行处理:
| class TestManager(object): |
| |
| def __enter__(self): |
| print("Entering") |
| |
| def __exit__(self, exc_type, exc_value, traceback): |
| print("Exiting") |
| if exc_type is not None: |
| print(f"Exception: {exc_value}") |
| with TestManager() as f: |
| print(1 / 0) |
| Entering |
| Exiting |
| Exception: division by zero |
| |
| |
| |
| ZeroDivisionError Traceback (most recent call last) |
| |
| Input In [14], in () | |
| 1 with TestManager() as f: |
| |
| |
| ZeroDivisionError: division by zero |
如果不想让异常继续抛出,只需要将.__exit__()
方法的返回值设为 True
:
| class TestManager(object): |
| |
| def __enter__(self): |
| print("Entering") |
| |
| def __exit__(self, exc_type, exc_value, traceback): |
| print("Exiting") |
| if exc_type is not None: |
| print(f"Exception: {exc_value}") |
| return True |
| with TestManager() as f: |
| print(1 / 0) |
| Entering |
| Exiting |
| Exception: division by zero |
清理临时文件: