上下文管理
__enter__
__exit__
文件的IO操作可以对文件对象使用上下文管理,使用with…as语法。
with open('test') as f:
pass
如果希望类也支持上下文管理,则需要定义两个函数。
class A:
def __init__(self):
print('init')
def __enter__(self):
print('enter')
def __exit__(self,exc_type,exc_val,exc_tb):
print('exit')
with A() as a:
print(a) # a 此时为None
# 输出:
# init
# enter
# None
# exit
进入with语句块时,会调用__enter__,退出with语句块时,调用__exit__。
因此,退出exit函数时,可以做清理工作,比如关闭文件描述符。即使异常退出,也会执行上下文。因此,涉及到资源申请的操作,最好使用上下文管理语句。
注意:先实例化,才能调用enter。因为enter是实例方法。 只有with语法时,才会和enter,exit等函数相关,如此才会开启上下文。
上下文管理对象
当一个对象同时实现了__enter__和__exit__方法时,它就属于上下文管理对象。
方法 | 意义 |
---|---|
__enter__ | 进入与此对象相关的上下文。如果存在此方法,with语法会把该方法返回值作为绑定到as子句中指定的变量上 |
__exit__ | 退出与此对象相关的上下文,包含三个参数:异常类型,异常的值,异常的traceback。 |
class Point:
def __init__(self):
print('init')
def __enter__(self):
print('enter')
def __exit__(self,exc_type,exc_val,exc_tb):
print('exit')
with Point() as f:
raise Exception('error') # 即使抛出异常,也会执行exit。因此,上下文管理是安全的。即使sys.exit也是安全的
print('do sth.')
注意:如果__exit__返回True,会压制异常。
函数的上下文和类的上下文
-
函数的上下文,是同一个对象:
f = open('1.py') with f as a: print(f == a) # 同一个对象 print(id(f),id(a))
-
类的上下文,则不同:
with A() as a: print(a) # None 返回__enter__的return值 with A(): # print(A()) # 返回实例 # a 和A() 是不同对象
上下文应用场景
-
增强功能
在代码执行的前后增加代码,以增强其功能。类似装饰器的功能。
-
资源管理
打开了资源需要关闭,例如文件对象、网络连接、数据库连接等。
-
权限验证
在执行代码之前,做权限验证。在__enter__中处理。
应用:使用上下文,实现函数计时器
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 f:
print(add(4,6))
contextlib.contextmanager
它是一个装饰器,装饰一个函数后可实现上下文管理。
而无需像类一样实现__enter__和__exit__ 方法。
它对装饰的函数是有要求的,必须有yield,也就是这个函数必须返回一个生成器,且只有yield一个值。
也就是这个装饰器接收一个生成器对象作为参数。
import contextlib
@contextlib.contextmanager
def foo():
print('enter') # 相当于__enter__()
yield 5 # yield的值只能有一个,相当于__enter__方法的返回值
print('exit') # 相当于__exit__()
with foo() as f:
print(f)
输出:
enter
5
exit
f接收了yield语句的返回值。
但是很明显无法保证exit的执行,如果产生了异常就直接退出。因此增加try finally。
import contextlib
@contextlib.contextmanager
def foo():
print('enter') # 相当于__enter__()
try:
yield 5 # yield的值只能有一个,相当于__enter__方法的返回值
finally:
print('exit') # 相当于__exit__()
with foo() as f:
raise Exception()
print(f)
如此保证了enter和exit的正常执行。
当yield发生处为生成器函数增加了上下文管理,这是为函数增加上下文机制的方式:
- 把yield之前的当做方法执行
- 把yield之后的当做__exit____方法执行
- 把yield的值作为__enter__的返回值
因此,如果业务逻辑简单可以使用函数加contextlib的装饰器方式,如果业务复杂,则使用类的方式加__enter__和__exit__方法方便。
反射相关的魔术方法
__getattr__
__setattr__
__delattr__
__getatrribute__
具体可参考《浅谈Python中的反射》