学习文件管理的时候我们都会用到with open(file) 语句来管理文件,在这个过程中文件一直保存打开的状态,这样做的好处是不用再单独去判断某种异常情况,也不用专门去执行文件关闭的指令了。
其实这里的with语句就是一个上下文管理器,语法结构如下:
语法:
with 上下文管理器对象 as f:
expression
试想一下,你打开一个文件,修改文件资源,保存并关闭修改后的文件资源。如果在关闭文件资源之前,你的代码发生了错误,很有可能导致文件不会被关闭,这会导致“资源泄漏”。在这种情况下,我们需要一个上下文管理器,保证无论代码是否出错,打开的'资源'最终都会被释放。
简单点说,就是在一个类里,实现了__enter__
和__exit__
的方法,这个类的实例就是一个上下文管理器。
1、__enter__() 进入上下文,返回当前对象或者与运行时上下文相关的其他对象。
一般用来处理操作前的内容。比如一些创建对象,初始化等。
如果with语句有as关键词存在,返回值会绑定在as后的变量上。
2、__exit__(exc_type, exc_val, exc_tb) 退出上下文,返回一个布尔值标示是否有需要处理的异常。
一般用来处理一些善后收尾工作,比如文件的关闭,数据库的关闭等。
如果在执行with语句体时发生异常,那退出时参数会包括异常类型、异常值、异常追踪信息,否则,3个参数都是None。
既然知道了上下文管理器要包含__enter__和__exit__方法,这个时候我们再来看下为什么说 with open(file) 语句就是一个上下文管理器了,
with open('mytest.txt','r') as f:
print(dir(f))
-------------------------------------------------------------
运行结果:
['_CHUNK_SIZE', '__class__', '__del__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_checkClosed', '_checkReadable', '_checkSeekable', '_checkWritable', '_finalizing', 'buffer', 'close', 'closed', 'detach', 'encoding', 'errors', 'fileno', 'flush', 'isatty', 'line_buffering', 'mode', 'name', 'newlines', 'read', 'readable', 'readline', 'readlines', 'reconfigure', 'seek', 'seekable', 'tell', 'truncate', 'writable', 'write', 'write_through', 'writelines']
我们很容易看到对象f具有 __enter__ 和 __exit__ 方法,所以是上下文管理器。
# 自定义上下文管理器
class MyContextManager:
def __enter__(self):
print("-- Starting the Manager --")
def __exit__(self, exc_type, exc_val, exc_tb):
print("-- Exiting the Manager --")
with MyContextManager():
print("-- In the Manager --")
------------------------------------------------------------------------
运行结果:
-- Starting the Manager --
-- In the Manager --
-- Exiting the Manager --
自定义一个上下文管理器 MyContextManager ,包含__enter__和__exit__方法。
分析运行结果可知其执行过程:
首先执行类中的__enter__()
方法,它总是在进入代码块前被调用的
接着就执行代码块——with
语句下面的代码块
离开的时候又调用类中的__exit__()
# 自定义带参数的上下文管理器
class MyContextManagerOpenDemo:
def __init__(self, filename, mode):
"""
实例化对象的时候必须带上的参数
"""
self.filename = filename
self.mode = mode
def __enter__(self):
"""
进入上下文管理器,打开指定文件,返回文件对象
"""
print('--- Starting the Manager ---')
self.open_file = open(self.filename, self.mode)
return self.open_file
def __exit__(self, exc_type, exc_val, exc_tb):
"""
退出上下文管理器,关闭文件对象。
"""
print('--- Exiting the Manager ---')
self.open_file.close()
with MyContextManagerOpenDemo('mytest.txt', 'r') as f:
print('--- In the Manager ---')
print(f.read())
-----------------------------------------------------------------------------
运行结果:
--- Starting the Manager ---
--- In the Manager ---
aaaaaaaaaaaaaaaa
--- Exiting the Manager ---
注意:
上面的demo里没有考虑异常情况。
上下文管理器包含的 __exit__ (exc_type, exc_val, exc_tb)方法中有三个参数,用来接收和处理异常,如果执行with语句时候发生异常,异常会被保存到这里。
使用 return 返回值决定捕获的异常是否继续向外抛出(没有return 就默认为 return False)。
# 抛出异常
class MyContextManagerOpenDemo:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
def __enter__(self):
print('--- Starting the Manager ---')
self.open_file = open(self.filename, self.mode)
return self.open_file
def __exit__(self, exc_type, exc_val, exc_tb):
print('--- Exiting the Manager ---')
self.open_file.close()
# 抛出异常
return False
with MyContextManagerOpenDemo('mytest.txt', 'r') as f:
print('--- In the Manager ---')
print(f.write('sss'))
---------------------------------------------------------------------------------
运行结果:
Traceback (most recent call last):
File "C:/Users/057776/PycharmProjects/python-test/python_basics/context_manager/context_manager_test.py", line 82, in
print(f.write('sss'))
io.UnsupportedOperation: not writable
--- Starting the Manager ---
--- In the Manager ---
--- Exiting the Manager ---
分析可知,mode=f,只有“读”权限,没有“写”权限,with语句的f.write('sss')执行异常,__exit__方法捕获异常, return False 抛出异常信息 io.UnsupportedOperation: not writable
# 不抛出异常
class MyContextManagerOpenDemo:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
def __enter__(self):
print('--- Starting the Manager ---')
self.open_file = open(self.filename, self.mode)
return self.open_file
def __exit__(self, exc_type, exc_val, exc_tb):
print('--- Exiting the Manager ---')
self.open_file.close()
print('Type: ', exc_type)
print('Value:', exc_val)
print('TreacBack:', exc_tb)
# 不抛出异常
return True
with MyContextManagerOpenDemo('mytest.txt', 'r') as f:
print('--- In the Manager ---')
print(f.write('sss'))
-------------------------------------------------------------------------
运行结果:
--- Starting the Manager ---
--- In the Manager ---
--- Exiting the Manager ---
Type:
Value: not writable
TreacBack:
使用装饰器来实现上下文管理器,使我们的代码更加优雅,更python范儿。
这里不做介绍,后续会用专门的文章介绍 contextlib 模块。
reference:
上下文管理器 - 《零基础学python》(第二版)