python之上下文管理器的实现__enter__、 __exit__

让对象支持with语句

在测试类中实现__enter__和__exit__,并使用with语句,观察其执行顺序

class Test:

    def __init__(self):
        print('__init__方法')

    def __enter__(self):
        print('__enter__方法')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('__exit__方法')


with Test():
    print('with体')

执行结果如下:

__init__方法
__enter__方法
with体
__exit__方法

总结:
当类实现内置方法__enter__()和__exit__(),使用with语句时,执行顺序为:

  1. 执行__init__()方法
  2. 执行__enter__()方法
  3. 执行with体
  4. 执行__exit__()方法

with语句中抛异常

在例子1的基础之上,让with语句体抛异常,观察其执行顺序

with Test():
    i=1/0 #抛异常
    print('with体')

执行结果如下:

__init__方法
__enter__方法
__exit__方法
Traceback (most recent call last):
  File "c:/Users/mym/Desktop/python/textfilereader.py", line 26, in <module>
    i=1/0
ZeroDivisionError: division by zero

总结:
在with语句中抛异常时,执行顺序为:

  1. 执行__init__()方法
  2. 执行__enter__()方法
  3. 执行with体抛异常
  4. 抛异常后,仍然执行__exit__()方法

忽略异常

方法__exit__()的返回值 与with语句 是否忽略异常 相关:

  • 返回False:抛出异常,默认返回False
  • 返回True:忽略异常

例子

改造上例代码,将方法__exit__()返回True,在with语句体抛异常,观察其执行过程:

def __exit__(self, exc_type, exc_val, exc_tb):
    print('__exit__方法')
    return True

with Test():
    i=1/0 #抛异常
    print('with体')

执行结果如下:

__init__方法
__enter__方法
__exit__方法

总结:
在with语句中抛异常时,执行顺序为:

  1. 执行__init__()方法
  2. 执行__enter__()方法
  3. 执行with体 抛异常,如果__exit__方法 返回True,忽略异常,返回False抛出异常,如有必要,由with语句之外的语句来处理异常
  4. 抛异常后,仍然执行__exit__()方法

with…as…变量

使用with ... as ...语句,观察 as变量:

with Test() as t:
    print(t)
    print('with体')

执行结果如下:

__init__方法
__enter__方法
None
with体
__exit__方法

总结:
在with ... as ...语句中,返回结果为None,而不是Test类对象

__enter__()返回值

在__enter__方法中返回数字1,并使用with ... as ...语句,观察 as 变量:

def __enter__(self):
    print('__enter__方法')
    return 1

with Test() as t:
    print(t)
    print('with体')

执行结果如下:

__init__方法
__enter__方法
1
with体
__exit__方法

总结:
在with ... as ...语句中,as后面变量的值,是__enter__方法的返回值,而不是Test类对象

一般__enter__方法返回self,当然具体情况具体分析

案例:读取配置文件类

在很多面向对象语言中(C++、JAVA、C#),都有读取配置文件类,(文件的“文件流”类,类名一般有file、stream、reader、writer等单词组成)
配置文件格式如下:

db_name=mysql
username=root
password=

在本例中,我们实现ConfigFileReader类,对open()方法进行封装,用于读取 配置文件 ,并且 实现上下文管理器 ,并 使用with语句 读取文件

class ConfigFileReader():
    def __init__(self, filename,encoding=None):
        self._file = open(filename, 'r',encoding=encoding)

    def __enter__(self):
        print('执行__enter__()')
        return self

    def __exit__(self, *args):
        print('执行__exit__()')
        self._file.close()


    def parse(self):
        ret={}
        for line in self._file:
            line=line.strip()
            pos=line.find("=")
            if pos>=0:
                ret[line[:pos]]=line[pos+1:]
            else:
                raise Exception("[%s] 没有=,格式错误"%line)

        return ret



if __name__ == "__main__":

    with ConfigFileReader(r'C:\Users\mym\Desktop\python\上下文管理器\dbconfig-utf8.txt',encoding="utf-8") as f:
        data=f.parse()

    for key,value in data.items():
        print("%s=%s"%(key,value))

总结:

  1. 为了实现上下文管理器,要实现__enter__()和__exit__()方法
  2. 在__enter__()方法中返回值赋给as语句的变量
  3. 在__exit__()方法执行关闭、清理等操作,并且返回True,忽略异常,返回False抛出异常

执行顺序:

  1. 执行__init__()方法
  2. 执行__enter__()方法
  3. 执行with体
  4. with体抛异常,仍然执行__exit__()方法

感谢文章:
https://blog.csdn.net/xc_zhou/article/details/80810111
https://www.cnblogs.com/huchong/p/8268765.html


原文出处:/show_1EF2OnMfHCsG.html