魔术方法之上下文管理
文件IO操作可以对文件对象使用上下文管理,使用with as语法。
with open('test') as f:
pass
仿照上例写一个自己的类,实现上下文管理
class A:
pass
with A() as f:#AttributeError:__enter__
pass
提示属性错误,没有__enter__
,看来需要这个类属性,某些版本会显示没有__exit__
上下文管理对象
当一个对象同时实现了__enter__()
和__exit__()
方法。他就属于上下文管理对象
方法 | 意义 |
---|---|
__enter__ | 当with后面的对象进入上下文,如果存在该方法,with语法会把该方法的返回值绑定到as子句中指定的变量上 |
__exit__ | 当with一句结束时,调用此对象的该方法, |
class Point:
def __init__(self):
print('init---------------')
def __enter__(self):
print('enter--------------')
def __exit__(self, exc_type, exc_val, exc_tb):#该方法后3个形参为上下文期间产生异常时的信息
print(exc_type,exc_val,exc_tb,sep='\n')
print('exit---------------')
with Point() as p:
1/0
print('with---------')
print('end-------------')
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lf7xeNp6-1573036581781)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571833942092.png)]
实例化对象时,并不会调用enter,进入with语句块调用__enter__
方法,然后执行with语句里面的内容。
当碰到除0异常时,程序会调用__exit__
方法,并把异常信息传入,如果此时该方法的返回值等效为True,则会压制异常。
with可以开启一个上下文运行环境,在执行前做一些准备工作,执行后做一些收尾工作
注意:with并不会开启一个新的作用域
上下文管理的安全性
接下来看一个极端例子
调用sys.exit()
,会退出当前解释器。
import sys
class Point:
def __init__(self):
print('init---------------')
def __enter__(self):
print('enter--------------')
return 10
def __exit__(self, exc_type, exc_val, exc_tb):#该方法后3个形参为上下文期间产生异常时的信息
print(exc_type,exc_val,exc_tb,sep='\n')
print('exit---------------')
with Point() as p:
print(p)
sys.exit(1)
1/0
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DWsOjKvS-1573036581782)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571835374448.png)]
从执行结果来看,with语句把__enter__
方法的返回值通过as子句绑定到p,之后运行其内部语句,当执行到sys.exit(1)
时退出了python的运行环境,但依然执行了__exit__
方法。
说明上下文管理很安全
练习
为一个加法函数计时
#方法1,使用函数装饰器显示该函数的执行时长
from functools import wraps
import datetime,time
def timeit(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
start = datetime.datetime.now()
ret = fn(*args,**kwargs)
delta = (datetime.datetime.now() - start).total_seconds()
print(delta)
return ret
return wrapper
@timeit
def add(x, y):
time.sleep(2)
return x + y
print(add(5, 6 ),add.__name__)
#方法2,使用上下文管理方法来显示该函数的执行时长
import datetime,time
from functools import wraps,update_wrapper
class TimeIt:
def __init__(self,fn):
self.fn = fn
def __enter__(self):
self.start = datetime.datetime.now()
return self.fn
def __exit__(self,exc_typy,exc_val,exc_tb):
delta = (datetime.datetime.now() - self.start).total_seconds()
print(delta)
def add(x, y):
time.sleep(2)
return x + y
with TimeIt(add) as fn:
fn(5, 6 )
上下文的应用场景
1.增强功能
在代码执行的前后增加代码,以增强其功能,类似装饰器。
2.资源管理
打开资源需要关闭,例如文件对象,网络对象,数据链接等。
3.权限验证
在执行代码前,做权限验证,在__enter__
中处理。
contextlib.contextmanager
contextlib.contextmanager是一个装饰器实现了上下文管理,装饰一个函数,而不像类一样实现__enter__
和__exit__
方法。
其对下面的函数有所要求:
必须有yield,也就时函数必须返回一个生成器,并且只有一个yield值。
也就是这个装饰器接收一个生成器作为参数。
from contextlib import contextmanager
import sys
@contextmanager
def foo():
print('enter---------')#相当于enter
try:
yield 5#yield的值只能由一个,作为enter方法的返回值
finally:
print('exit-----------')#相当于exit
with foo() as fn:
print(fn)
sys.exit(1)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ydnKurTp-1573036581783)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571838243491.png)]
当yield发生处为生成器增加一个上下文管理,这是为函数增加上下文机制的方式。上例中用异常捕获语句的目的是当with结构内部发生异常时,保证能够执行print(exit)
- 把yield之前的当做
__enter__
方法执行 - 把yield之后的当做
__exit__
方法执行 - 把yield的值做为
__enter__
的返回值
总结:如果业务逻辑简单可以使用函数加contextlib.contextmanager装饰器方式,如果业务复杂,用类的方式加__enter__
和__exit__
方法方便
- 把yield之前的当做
__enter__
方法执行 - 把yield之后的当做
__exit__
方法执行 - 把yield的值做为
__enter__
的返回值
总结:如果业务逻辑简单可以使用函数加contextlib.contextmanager装饰器方式,如果业务复杂,用类的方式加__enter__
和__exit__
方法方便