1.引入
我们时常会看到网上很多人写文件读写的代码的时候,常常是这么写的:
with open('test.txt', 'r') as f:
print(f.read())
# 其它操作
这个with有什么用呢?
我们来看看不用这种写法我们怎么写。
假如我们直接这么写:
f = open('test.txt', 'r')
print(f.read())
# 其它操作
f.close()
这种写法有什么问题呢?
假如在做其它操作的时候,出错了,程序终止,f.close()不会执行,则f这个句柄会一直得不到释放,要是并发操作,这个就很严重了。
所以我们通常会这么写:
try:
f = open('test.txt', 'r')
except Exception:
pass
finally:
f.close()
但是每个操作都要这么写,就很繁琐了。
with open('test.txt', 'r') as f
的作用在于,如果发生异常,它会帮你执行f.close()。它的原理就是上下文管理器。
这里还有个需要注意的问题,假如你用write()来写入文件,操作系统通常是先写到内存,空闲的时候再写入磁盘。只有调用close()方法时,操作系统才保证把没有写入的数据全部写入磁盘。
再举个例子,假如你写了一个类,但是里面可能会抛出异常,如果异常你需要做一些收尾操作,比如关闭数据库,关闭文件句柄,关闭某些长连接。一个你可以try catch,两个你也忍,多少太多地方用到了你就晕了,复制黏贴复制黏贴,累。
于是上下文管理器应运而生。
2. 上下文管理器--with关键字
- 基本语法:
with EXPR as VAR:
BLOCK
运行逻辑:
1.执行EXPR的上下文管理器的__enter__
方法,as VAR可以省略,如果不省略,则将__enter__
方法的返回值赋值给VAR
2.执行代码块BLOCK
3.调用上下文管理器中的的__exit__
方法例子:
class FileHelper:
def __init__(self, file_name):
self.file_name = file_name
self.file_handler = None
def __enter__(self):
print('-' * 10, 'enter', '-' * 10)
self.file_handler = open(self.file_name, 'a+')
return self.file_handler
def __exit__(self, exc_type, exc_val, exc_tb):
print('-' * 10, 'exit', exc_type, exc_val, exc_tb)
with FileHelper('test.txt') as f:
for line in f:
print(line)
raise IOError
执行结果:
---------- enter ----------
Traceback (most recent call last):
---------- exit <class 'OSError'> <traceback object at 0x0000027AF786AF88>
File "D:/PythonProjects/Test.py", line 30, in <module>
raise IOError
OSError
可以看到,结果输出了enter和exit的打印输出。
3.异步上下文管理器--async with
旧的上下文管理器是这样实现的:
class Manager:
async def __aenter__(self):
async def __aexit__(self, exc_type, exc, tb):
新语法:
async with EXPR as VAR:
BLOCK
例子:
async def do_task(domain, pageUrl):
async with aiohttp.ClientSession() as session:
async with session.request('GET', pageUrl) as resp:
if resp.status != 200:
raise Exception('http error, url:{} code:{}'.format(pageUrl, resp.status))
html = await resp.read() # 可直接获取bytes
这是我另一篇文章Golang协程与Python协程速度比较贴过来的代码,await 相当于 yield from
。
有疑问加站长微信联系(非本文作者)