上下文管理
文件IO操作可以对文件对象使用上下文管理,使用with...as语法。
with open('test',w+) as f: f.write('abc')
仿照上例写一个自己的类,实现上下文管理
class Point: pass def __enter__(self): pass def __exit__(self, exc_type, exc_val, exc_tb): pass with Point() as p: pass
上下文管理对象
当一个对象同时实现了__enter__(),__exit__()方法,它就属于上下文管理的对象
方法 | 意义 |
__enter__ | 进入此对象相关的上下文,如果存在该方法,with语句会把该方法的返回值作为绑定到as子句中指定的变量上 |
__exit__ | 退出与此对象相关的上下文 |
import time class Point: def __init__(self): print(1,'init~~~~~~~~~~~') time.sleep(1) print(2,'init end~~~~~~~') def __enter__(self): print(3,'enter~~~~~~~~~~~') def __exit__(self, exc_type, exc_val, exc_tb): print(4,'exit~~~~~~~~~~~') with Point() as f: print(5,'in with ----------------') time.sleep(1) print(6,'with end------------') print(7,'ending-----------------------') #输出 1 init~~~~~~~~~~~ 2 init end~~~~~~~ 3 enter~~~~~~~~~~~ 5 in with ---------------- 6 with end------------ 4 exit~~~~~~~~~~~ 7 ending-----------------------
实例化对象的时候,并不会调用enter,进入with语句块调用语句块调用__enter__方法,然后执行语句体,最后离开with语句块的时候,调用__exit__方法。
with可以开启一个上下文运行环境,在执行前做一些准备工作,执行后做一些收尾工作。
注意,with并不开启一个新的作用域。
上下文管理的安全性
看看异常对上下文的影响。
import time class Point: def __init__(self): print('init~~~~~~~') time.sleep(1) print('init end~~~~') def __enter__(self): print('enter~~~~~~~') def __exit__(self, exc_type, exc_val, exc_tb): print('exit~~~~~~~~~') with Point() as f: print('in with~~~~~~~~~~~~') 1/0 time.sleep(1) print('with over') print('==============end==============') #输出 init~~~~~~~ init end~~~~ enter~~~~~~~ in with~~~~~~~~~~~~ exit~~~~~~~~~ Traceback (most recent call last): File "D:/Python/test.py", line 322, in1/0 ZeroDivisionError: division by zero
可以看出在enter和exit照样执行,上下文管理是安全的。
极端例子,调用sys.exit(),他会退出当前解释器。
打开Python解释器,在里面敲入sys.exit(),窗口直接关闭了,也就是说碰到这句Python运行环境直接退出了。
从上面执行结果来看,依然执行了__exit__函数,哪怕是退出了Python运行环境,说明上下文管理是很安全的。
class Point: def __init__(self): print('init') def __enter__(self): print('enter') return self def __exit__(self, exc_type, exc_val, exc_tb): print('exit') f = open('test3.py') with f as p: print(f) print(p) print(f is p) print(f == p) p = f = None print('------------------------') p = Point() with p as f: print('in with~~~~~~~~~~') print(p == f) print('with over') print('======end=======')
with语法,会调用with后的对象的__enter__方法,如果有as,则将该方法的返回值赋值给as子句的变量。
上例等价于 f = p.__enter__()
__enter__方法没有其他参数
__exit__方法有三个参数:
__exit__(self, exc_type, exc_val, exc_tb)
这三个参数都与异常有关。
如果该上下文退出时没有异常,这三个参数都为None
如果有异常,参数意义如下:
exec_type:异常类型
exec_val:异常值
exec_tb:traceback,异常的追踪信息
__exit__:方法返回一个等效True的值,则压制异常,否则继续抛出异常。
class Point: def __init__(self): print('init~~~~~~~~~~~~~~') def __enter__(self): print('enter~~~~~~~~~~~~~') return self def __exit__(self, exc_type, exc_val, exc_tb): print(1,exc_type) print(2,exc_val) print(3,exc_tb) print('exit~~~~~~~~~~~~~~') # return 123 #等效为True,则压制异常 # return 'abc' return [] #等效为False则抛出异常 p = Point() with p as f: print('in with ~~~~~~~~~~~') raise Exception('Error') print('with over') print('========end=======') #等效False输出 init~~~~~~~~~~~~~~ enter~~~~~~~~~~~~~ in with ~~~~~~~~~~~ 1 <class 'Exception'> 2 Error 3exit~~~~~~~~~~~~~~ Traceback (most recent call last): File "D:/Python/test.py", line 383, in raise Exception('Error') Exception: Error #等效True输出 init~~~~~~~~~~~~~~ enter~~~~~~~~~~~~~ in with ~~~~~~~~~~~ 1 <class 'Exception'> 2 Error 3 exit~~~~~~~~~~~~~~ ========end=======
为加法函数计时
方法1、使用装饰器显示该函数的执行时长
方法2、使用上下文管理方法来显示该函数的执行时长
装饰器实现:
import time import datetime from functools import wraps from functools import update_wrapper def timeit(fn): @wraps(fn) def wrapper(*args,**kwargs): '''This is wrapper functioin''' start = datetime.datetime.now() ret = fn(*args,**kwargs) delta = (datetime.datetime.now() - start).total_seconds() print('{} took {}s'.format(fn.__name__,delta)) # update_wrapper(wrapper,fn) return ret return wrapper @timeit # add=timeit(add) def add(x,y): '''This is add function''' time.sleep(2) return x + y print(add(4,5))
上下文实现
import time import datetime from functools import update_wrapper def timeit(fn): def wrapper(*args,**kwargs): start = datetime.datetime.now() ret = fn(*args,**kwargs) delta = (datetime.datetime.now() - start).total_seconds() print("{} took {}s".format(fn.__name__,delta)) update_wrapper(wrapper,fn) return ret return wrapper @timeit def add(x,y): time.sleep(2) return x + y class Timeit: def __init__(self,fn): self.fn = fn def __enter__(self): self.start = datetime.datetime.now() return self.fn def __exit__(self, exc_type, exc_val, exc_tb): delta = (datetime.datetime.now() - self.start).total_seconds() print("{} took {}s".format(self.fn.__name__,delta)) with Timeit(add) as fn: print(add(4,5))
另一种实现,使用可调用对象实现
import time import datetime from functools import update_wrapper def timeit(fn): def wrapper(*args,**kwargs): start = datetime.datetime.now() ret = fn(*args,**kwargs) delta = (datetime.datetime.now() - start).total_seconds() print("{} took {}s".format(fn.__name__,delta)) update_wrapper(wrapper,fn) return ret return wrapper @timeit def add(x,y): time.sleep(2) return x + y class Timeit: def __init__(self, fn): self.fn = fn def __enter__(self): self.start = datetime.datetime.now() return self def __exit__(self, exc_type, exc_val, exc_tb): delta = (datetime.datetime.now() - self.start).total_seconds() print("{} took {}s".format(self.fn.__name__,delta)) def __call__(self, x, y): return self.fn(x,y) with Timeit(add) as fn: print(add(4,5))
将类当成装饰器用
import time import datetime from functools import wraps,update_wrapper class Timeit: def __init__(self,fn): self.fn = fn # self.__doc__ = fn.__doc__ # update_wrapper(self,fn) wraps(fn)(self) def __call__(self, *args, **kwargs): self.start = datetime.datetime.now() ret = self.fn(*args,**kwargs) self.delta = (datetime.datetime.now() - self.start).total_seconds() print("{} took {}s call".format(self.fn.__name__,self.delta)) return ret @Timeit # add = Timeit(add) def add(x,y): '''This is add function''' time.sleep(2) return x + y print(add(4,5)) print(add.__doc__)
上面的类即可以使用在上下文管理,有可以用做装饰器
上下文管理应用场景
1.增强功能
在代码执行的前后增加代码,以增强其功能,类似装饰器的功能
2.资源管理
打开了资源需要关闭,例如文件对象、网络连接、数据库连接等
3.权限验证
在执行代码之前,做权限的验证,在__enter__中处理
contextlib.contextmanager
contextlib.contextmanager:它是一个装饰器实现上下文管理,装饰一个函数,而不像类一样实现__enter__和__exit__方法
对下面的函数有要求:必须有yield,就是函数必须返回一个生成器,且只有yield一个值。
也就是这个装饰器就收一个生成器对象作为参数。
import contextlib @contextlib.contextmanager def foo(): print('enter') yield 123 print('exit') with foo() as f:
#raise Exception print(f) enter 123 exit
f接收yield语句的返回值。
上面的程序看似不错,但是,增加异常,发现不能保证exit执行,需增加try finally
import contextlib @contextlib.contextmanager def foo(): print('enter') try: yield 123 finally: print('exit') with foo() as f: raise Exception('error') print(f)
编写一个支持上下文的函数timeit,完成对add函数的计时
from contextlib import contextmanager import time import datetime @contextmanager def timeit(): print('enter') start = datetime.datetime.now() try: yield finally: print('exit') delta = (datetime.datetime.now() - start).total_seconds() print(delta) def add(x,y): time.sleep(2) return x + y with timeit(): # 1/0 #此处异常,后续的语句将不再执行 add(4,5)