03-Python-with语句块-上下文管理器

文章讲述了Python中with语句在文件读写中的应用,强调了它在处理异常和自动资源管理方面的优势,以及上下文管理器协议的实现原理和两种常见实现方法:自定义类和contextlib模块的@contextmanager装饰器。
摘要由CSDN通过智能技术生成

1. 引入

想一想,我们使用with语法块最常见的是在文件读写时,那么先从文件读写入手,看看为什么使用with语法块对文件读写最合适。

1.普通的文件读写方式

f = open("test.txt","w")  #以读的方式打开文件
f.write("测试")   #写入数据
f.close()  #关闭文件,释放文件资源

但是这种方式有很大的缺陷:当我们向文件写入数据的过程中出现异常,程序会立马终止报错,此时f.close()不会执行,就导致该文件没有被关闭上,导致文件资源无法释放,文件内容的安全性收到威胁。

2. 针对1的改进方法:使用异常处理

try:
    f = open("test.txt","w")
    f.write("测试")
    qwe  # 故意设置的异常点,导致程序出现异常
except BaseException:
    f.close()
    print("关闭了")
else:
    f.close()

 这种方式下,很好的处理了文件读写出现异常时的关闭文件的难题,当然使用try...finally...也可以实现。但是每次对文件操作时都要写这么多东西,显的太麻烦了。

3. 接下来就到比较合适的方法了:with语法块

with open("test.txt","w") as f:
    f.write("测试")

很简单,代码量也很少。

2. with语法结构

with语法块的结构:

with 实现上下文管理器协议的对象 [as target]:
    with body

[as target] 是可选参数,可写可不写。

最最重要的是with后面跟的对象,现在开始讨论这个特殊的实现上下文管理器协议的对象

3. 上下文管理器协议

首先,文件读写能用with语法块,是因为文件对象就是一个实现了上下文管理器协议的对象

一个实现上下文管理器协议的类创建的对象就是实现了上下文管理器协议的对象

创建一个实现上下文管理器协议的类必须要实现两种方法:

__enter__():在进入with语法块之前执行,返回值会赋值给target

__exit__():在退出with语法块时执行,通常用于异常处理

先简单搞个例子实现一下:

class A():
    def __enter__(self):
        print("enter func")
        return 1
    def __exit__(self,etype,value,tb):
        print("exit func")
        print(etype,value,tb)
a = A()
with a as f:
    print(f)

"""
运行结果:
enter func
1
exit func
None None None
"""

要注意的一点是__exit__()方法要传递四个参数,后面的三个分别是出现异常的类型,异常信息

异常堆栈信息。证实了__exit__()方法也用于处理异常。没有异常时,这三个变量值都是None,

下面简单实现一个存在异常的例子:

class A():
    def __enter__(self):
        print("enter func")
        return 1
    def __exit__(self,etype,value,tb):
        print("exit func")
        print(etype,value,tb)
a = A()
with a as f:
    a = 1/0  #异常点,除零异常
    print(f)

"""
运行结果:
enter func
exit func
<class 'ZeroDivisionError'> division by zero <traceback object at 0x0000020400B7CDC0>
"""

可以发现没异常时会执行__exit__(),有异常也会执行__exit__()

可以发现print(f)并未执行,可知一旦发生异常立马终止执行

前面讲了with语法块中[as target]是可选参数,用于接收__enter__()的返回值,那么当__enter__()没有返回值是,自然就不需要用as target

4. 另外一种实现上下文管理的方法

除了使用__enter__(),__exit__()方法实现上下文管理,也可以使用contextlib模块提供的contextmanager装饰器来装饰函数,使其实现上下文管理的功能,需要搭配yield。

接下来简单举个例子:

from contextlib import contextmanager
@contextmanager
def test():
    print("before")
    yield 12
    print("after")
with test() as f:
    print(f)

"""
运行结果:
before
12
after
"""

具体实现流程:

1. 首先由于contextmanager的装饰,test函数具备了上下文管理的功能,python万物皆对象,函数也是对象,故函数的调用也可以放在with后面

2.  yield 12 之前的相当于__enter__(),会在进入with语法块之前执行

3. yield 12 将yield后面的东西赋给f,即把12赋给f

4. yield 12 后面的相当于__exit__(),在退出with语法块时执行,但是要注意的一点时,他没有异常处理的能力,需要自己编写,否则当test函数内部发生异常时,yield后面的代码不会执行

补上异常处理的能力:

from contextlib import contextmanager
@contextmanager
def test():
    print("before")
    try:
        yield 12
        a = 1/0
    finally:
        print("after")
with test() as f:
    print(f)

可以看出,其实使用contextmanager装饰器的方法显得更简便一点 

5. 应用场景 

上下文管理器非常适用于用完后需要释放资源的场景,例如文件操作,socket

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值