Python中的__enter__和__exit__

Python
Author

metseq

Published

November 8, 2022

1 __enter____exit__是什么

当在类中实现__enter____exit__方法的使用,这个类就可以用with调用了,调用with的时候,返回__enter__的结果,退出with的时候,执行__exit__的代码。

平时用with最多的可能是打开一个文件,with返回文件的句柄(file handle),退出with的时候关闭文件。

如下代码:

# 建一个测试文件
! echo -e 'line1\nline2\nline3' > sample.txt
! cat sample.txt
line1
line2
line3
# 使用python读取文件
with open('sample.txt', 'r') as f:
    for line in f:
        print(line, end='')        
line1
line2
line3
# 查看f是否关闭了,返回True,表示f关闭了
f.closed
True

如果不用with,需要手动关闭文件。

f = open('sample.txt', 'r')
# f还没有关闭
f.closed
False
# 使用close()方法关闭文件后,文件就关闭了
f.close()
f.closed
True

2 自已写一个处理文件的类

class MyOpen():
    def __init__(self, file, mode):
        self.file = file
        self.mode = mode
    
    def __enter__(self):
        self.f = open(self.file, self.mode)
        return self.f
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.f.close()
with MyOpen('sample.txt', 'r') as f:
    for line in f:
        print(line, end='')        
line1
line2
line3
f.closed
True
Tip

注意上面的MyOpen类中的__exit__方法有4个参数,self不必说,后面三个参数是什么呢?

3 __exit__的参数

class SafeDivide():
    def __init__(self, a, b):
        self.a = a
        self.b = b
    
    def __enter__(self):
        print('exec __enter__')
        print(f'{self.a} / {self.b} = ', end='')
        return self
    
    def divide(self):
        return self.a / self.b
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print('\n------------')
        print('exec __exit__')
        print(f"exec_type: {exc_type}")
        print(f"exc_val: {exc_val}")
        print(f"exc_tb: {exc_tb}")
with SafeDivide(1, 2) as d:
    print(d.divide())
    print('啦啦啦')
exec __enter__
1 / 2 = 0.5
啦啦啦

------------
exec __exit__
exec_type: None
exc_val: None
exc_tb: None

如果执行没有问题,exc_type, exc_val, exc_tb都是None

with SafeDivide(1, 0) as d:
    print(d.divide())
exec __enter__
1 / 0 = 
------------
exec __exit__
exec_type: <class 'ZeroDivisionError'>
exc_val: division by zero
exc_tb: <traceback object>
ZeroDivisionError: division by zero

如果执行对象的方法出现问题,就会直接执行__exit__方法,exc_type, exc_val, exc_tb包含的就是错误类型,错误值,错误的回溯信息。

Note

出错后不会执行后面的语句,比如这里的print('啦啦啦'),而是直接执行__exit__