with
with关键字是一个流操作,可以调用上下文管理器对象,也就是只要实现了上下文管理器协议就可以使用with关键字调用
应用场景:
对于一些事先需要准备数据,事后需要数据清理工作的任务使用with关键字很方便。
关于with很好的例子就是文件操作,打开文件获得一个句柄,对文件进行操作,关闭文件
xiaogu = "xiaogu is the man of imposing looking"
f = open("./xiaogu.txt", "w")
contents = f.write(xiaogu)
f.close()
以上代码中先以写的方式打开文件,再往文件中写入内容,最后再关闭文件句柄。
现在这里就会存在一个问题,如果打开xiaogu.txt 文件时正常打开了,但是再写入文件的时候出现异常,即写入的不是一个字符串,而是列表或者字典类型,这样在没有关闭文件句柄的情况下抛出异常,如果这种情况大量存在,到最后可能连正常的文件操作都会出现问题。
解决办法1:使用异常捕获try/except/finilly
try:
xiaogu = "xiaogu is the man of imposing looking"
f = open("./xiaogu.txt", "w")
contents = f.write(xiaogu)
except Exception as e:
pass
finally:
f.close()
以上这种方法可以解决文件读写造成异常没有及时关闭的情况,但是代码冗余
解决办法2:使用python内置的with关键字
xiaogu = "xiaogu is the man of imposing looking"
with open("./xiaogu.txt", "w") as f:
f.write(xiaogu)
with使用很少的代码同样可以解决文件异常关闭的问题,也就是说即使文件读写出现异常依然可以关闭文件句柄,具体with的工作原理,先来看一个概念:上下文管理器
上下文管理器
上下文:就是在需要执行代码的前边的代码叫上文,在需要执行的代码的后边的代码叫下文。
上下文管理器:如果一个对象中实现了__enter__()方法和__exit__()方法,那么这个对象就是一个上下文管理器,其中__enter__()方法返回资源对象,exit()方法主要是做一些善后工作
自定义一个上下文管理器:
class Work():
def __init__(self, name, work):
self.name = name
self.work = work
def __enter__(self):
print(self.name)
return self
def func(self):
print("xiaogu is the man of imposing looking")
print(1/0)
def __exit__(self, exc_type, exc_val, exc_tb):
print(self.work)
with Work("xiaogu", "testing") as f:
f.func()
输出:
xiaogu
xiaogu is the man of imposing looking
testing
with的工作原理:
在代码执行到with Work(“xiaogu”, “testing”) as f:时,with首先会先查看Work()对象是不是一个上下文管理器对象,即Work()对象中是否实现了__enter__()方法和__exit__()方法,如果没有这两个方法,with会直接抛异常,如果有这两个方法,with会首先调用Work()对象中的__enter__()方法,把自身对象的引用返回,并把返回值会赋给f,f在调用work()对象中的其他方法时出现异常,程序还是会向外抛出异常,但是程序也会自动调用__exit__()方法,处理善后工作。
另一种实现上下文管理器的方式:使用python内置的contextlib库
import time
from contextlib import contextmanager
@contextmanager
def func():
print("xiaogu")
yield
print("haoshuai")
with func(): # 执行此处时,xiaogu会首先输出,3秒后haoshuai继续输出
time.sleep(3)
pass
输出:
xiaogu
haoshuai
在以上代码中,通过装饰器的形式实现上下文管理器,这种方式只需要定义一个函数,加上contextmanager装饰器即可,在函数中使用yield关键字,在yield以上的内容相当于是在__enter__()方法中执行,yield后边的值为返回值,如果没有则说明没有返回值,yield以后的代码相当于__exit__()方法中执行