魔术方法之上下文管理

魔术方法之上下文管理

文件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__方法方便

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值