定义
任何实现了上下文协议(也就是实现了__enter__和__exit__方法)的对象即为上下文管理器。所以也可以认为上下文管理器是一个包装任意代码块的对象。
使用场景及常用的上下文管理器
事实上上下文管理器的任务是 – 代码块执行前准备,代码块执行后收拾。常见用with关键字去实现上下文管理器,常用于文件处理上面。上下文管理器常常用于异常处理
好处
在写代码时,我们希望把一些操作放到一个代码块中,这样在代码块中执行时就可以保持在某种运行状态,而当离开该代码块时就执行另一个操作,结束当前状态。
举一个最浅显的例子,用open打开文件之后需要手动的去closed文件,但是如果是使用with关键字打开文件(相当于是使用的上下文管理器去打开的一个上下文对象),就不需要我们手动的去closed文件,因为上下文管理器会帮我们去处理这些后续工作。也可以认为上下文管理器是确保资源正确清理的一种方式。
上下文管理器的目的就是规定对象的使用范围,如果超出范围就采取“处理”,这一功能是在Python3.5之后引进的,它的优势在于可以使得你的代码更具可读性,且不容易出错。
简单来说,也就是上下文管理器能更好地实现对资源的自动创建与自动释放,允许你在有需要的时候,精确地分配和释放资源。
自定义的上下文管理器及应用
实现上下文管理器可以有两种方式,一种为基于类的实现,一种为基于生成器的实现
上下文管理器的实现
基于类的实现
基于类的实现需要实现两个方法:enter()和_exit_()
enter():负责进入代码块的准备工作,进入前被调用;
exit():负责离开代码块的善后工作,离开后被调用;
用于异常捕获,让程序能正常运行下去:
基于类实现上下文管理器:
class BubbleException(object):
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_val:
print(f"Bubbling up excetion: {exc_val}")
return False
with BubbleException():
print(1+1)
with BubbleException():
print(1/0)
print("发生了异常")
运行截图:
因为产生了异常,所以该语句不能正常执行。但是将___exit__里面的return值改为true,则就能正常运行。即就是将异常截取,不让其传播
class BubbleException(object):
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_val:
print(f"Bubbling up excetion: {exc_val}")
return True
with BubbleException():
print(1+1)
with BubbleException():
print(1/0)
print("发生了异常")
2.希望只捕获到当前的异常,不捕获其子类的异常
####4.上下文管理器捕获一个异常,但是不希望捕获它的子类异常
class valueerrorsub(ValueError):
pass
class HandleValueError():
def __init__(self):
self.name="hxy"
def __enter__(self):
return self #有实例对象的时候一定要返回本身(as name),否则name对象不是一个对象。
def __exit__(self, exc_type, exc_val, exc_tb):
if not exc_type:
return True
if exc_type==ValueError:
#if issubclass(exc_type,ValueError): ###判断exc_type是不是Valueerror或者是其子类
print(f"Handling ValueError:{exc_val}")
return True
##让其他错误传播下去
return False
with HandleValueError() as h:
print(h.name)
raise ValueError("value error")
with HandleValueError() as b:
print(b.name)
运行截图:
此时抛出一个子类异常:
class valueerrorsub(ValueError):
pass
class HandleValueError():
def __init__(self):
self.name="hxy"
def __enter__(self):
return self #有实例对象的时候一定要返回本身(as name),否则name对象不是一个对象。
def __exit__(self, exc_type, exc_val, exc_tb):
if not exc_type:
return True
if exc_type==ValueError:
#if issubclass(exc_type,ValueError): ###判断exc_type是不是Valueerror或者是其子类
print(f"Handling ValueError:{exc_val}")
return True
##让其他错误传播下去
return False
with HandleValueError() as h:
print(h.name)
raise ValueError("value error")
with HandleValueError() as b:
print(b.name)
raise valueerrorsub("valuesub error")
运行结果:
可以看到子类异常也传播出去了
基于生成器的实现:
contextlib模块专门用于这个目的
用contextlib模块+生成器函数yield去自定义一个上下文管理器
##3用contextlib去实现上下文管理器
import contextlib
@contextlib.contextmanager ###使用contecxtlib加生成器去实现上下文管理器
def context():
print("entering the zone") #yield之前的内容相当于enter里面的内容。
try:
yield
except Exception as e: #yield之后就是exit里面的内容
print(f"with error {e}")
else:
print("with no error")
with context():
print("use context")
raise ValueError("asdg") ####强制抛出异常
运行结果:
自定义上下文管理器和自定义shell异常类的运用
###编写一个shellException异常,返回状态码及错误详情
###编写run_command函数,实现运行shell命令功能,返回stdout
##编写上下文管理器,处理指定类型的错误码,如错误码为1则忽略次错误
from subprocess import Popen, PIPE, STDOUT
import os
def run_command(command):
currentPath = os.path.dirname(os.path.realpath(__file__))
p = Popen(command, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True, cwd=currentPath)
output, errors = p.communicate()
resultcode=p.returncode ##得到执行命令的状态码
context=bytes(errors).decode("utf-8") ###得到错误详情
result=bytes(output).decode("utf-8") ###得到执行的正确结果
if resultcode !=0: ###如果有错误,则抛出自定义异常
raise shellException(resultcode,context)
return result ###返回执行命令的结果
###shellException异常
class shellException(Exception):
def __init__(self,code,content):
self.code=code
self.content=content
def get_error_info(self):
return [self.code,self.content]
###上下文管理器
class shellContext():
def __init__(self,command):
self.command=command
def __enter__(self):
return self
def run_command(self):
self.result=run_command(self.command)
print("执行结果为:\n",self.result)
def __exit__(self, exc_type, exc_val, exc_tb):
print(exc_type)
if exc_type: ##捕捉到异常
print("已经捕捉到异常")
if exc_val.code!=1:
print("错误详情:",exc_val.content)
return True
return False
command=input("请输入你的操作:")
with shellContext(command) as f:
f.run_command()
运行截图(由于使用了linux命令,因此需要在linux环境下运行):