魔术方法-上下文管理

魔术方法

上下文管理

文件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的值,则压制异常;否则,继续抛出异常

练习
为加法函数计时

  1. 使用装饰器显示该函数的执行时长
  2. 使用上下文管理来显示该函数的执行时长
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__方法方便
上一篇:traceback:让你更加灵活地处理python的异常


下一篇:python module -- sys