魔术方法
上下文管理
文件IO操作可以对文件对象使用上下文管理,使用with…as语法
上下文管理对象
方法 | 意义 |
---|---|
__enter__ | 进入与此对象相关的上下文.如果存在该方法,with语法会把该方法的返回值作为绑定到as子句中指定的变量上 |
__exit__ | 退出与此对象相关的上下文 |
实例化对象的时候,并不会调用enter,进入with语句块调用__enter__方法,然后执行语句体,最后离开with语句块的时候,调用__exit__方法
with可以开启一个上下文运行环境,在执行前做一些准备工作,执行后做一些收尾工作
注意,with并不开启一个新的作用域
上下文管理的安全性
class Point:
def __init__(self):
print('init')
def __enter__(self):
print("enter")
def __exit__(self,exc_type,exc_val,exc_tb):
print('exit')
f = Point()
with f as p:
import sys
sys.exit(1)
print(f==p)
从执行结果来看,依然执行__exit__函数,哪怕是退出python运行环境
说明上下文管理很安全
with语句
class Point:
def __init__(self):
print('init')
def __enter__(self):
print("enter")
def __exit__(self,exc_type,exc_val,exc_tb):
print('exit')
#return 1 #压制异常
p = open('t1.py')
with p as f:
print(f)
print(p)
print(f is p) #True
print(f == p) #True
f = Point()
with f as p:
print(f==p) #Flase
问题在__enter__方法上,它将自己的返回值赋给f,修改上例
with语法,会调用with后的对象__enter__方法,如果有as,则将该方法的返回值赋给as子句的变量
上例,可以等价为f = p.__enter__()
方法的参数
enter,没有其他参数
exit,方法有三个参数(self,exc_type,exc_val,exc_tb)
这三个参数都与异常有关
如果该上下文退出时没有异常,这三个参数都为None
如果有异常,参数意义如下
exc_type,异常类型
exc_val,异常的值
exc_tb(traceback),异常的追踪信息
__exit__方法返回一个等效True的值,则压制异常;否则,继续抛出异常
练习
为加法函数计时
- 使用装饰器显示该函数的执行时长
- 使用上下文管理来显示该函数的执行时长
import datetime
import time
from functools import wraps,update_wrapper
def lagger(fn):
@wraps(fn)
def warrapr(*args,**kwargs):
statr = datetime.datetime.now()
ret = fn(*args,**kwargs)
datel = '{}s'.format((datetime.datetime.now()-statr).total_seconds())
print(datel)
return ret
return warrapr
@lagger
def add(x,y):
"""12312"""
time.sleep(2)
return x+y
print(add(10,10))
class Point:
"""abddadss"""
def __init__(self,fn):
self.fn = fn
update_wrapper(self,fn)
# self.__name__ = fn.__name__
# self.__doc__ = fn.__doc__
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('{}s'.format(delta))
with Point(add) as f:
print(add(4,5))
用类当字符串
import datetime
import time
from functools import wraps,update_wrapper
class Point:
"""abddadss"""
def __init__(self,fn):
self.fn = fn
update_wrapper(self,fn) #文档字符串
# self.__name__ = fn.__name__
# self.__doc__ = fn.__doc__
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(delta)
# print(exc_tb)
# print(exc_type)
# print(exc_val)
# print("~~~~~~~~~~~")
def __call__(self, *args, **kwargs):
statr = datetime.datetime.now()
ret = self.fn(*args,**kwargs)
datel = (datetime.datetime.now()-statr).total_seconds()
print(datel)
return ret
@Point
def add(x,y):
"""12312"""
time.sleep(2)
return x+y
print(add(10,10))
上面的类可以用在上下文管理,也可以用作字符串
上下文管理场景
1.增强功能
在代码执行的前后增加代码,以增强其功能.类似装饰器的功能
2.资源管理
打开了资源需要关闭,例如文件对象 网络连接 数据库连接等
3.权限验证
在执行代码之前,做权限的验证,在__enter__中处理
contextlib.contextmanager
它是一个装饰器实现上下文管理,装饰一个函数,而不用像类一样实现__enter__和__exit__方法
对下面的函数有要求:必须有yield,也就是这个函数必须返回一个生成器,且只有yield一个值
也就是说这个装饰器接收一个生成器对象作为参数
import contextlib
@contextlib.contextmanager
def foo():
print('abc')
try:
yield
finally:
print('123')
with foo() as f:
raise Exception()
print(f)
f接收yield语句的返回值
增加异常
增加try finally
当yield发生处为生成器函数增加了上下文管理.这是为函数增加上下文机制的方式
- 把yield之前的当做__enter__方法执行
- 把yield之后的当做__exit__方法执行
- 把yield的值为__enter__的返回值
总结
如果业务简单可以使用contextlib.contextmanager装饰器方式,如果业务复杂,用类的方式加__enter__和__exit__方法方便