上下文管理器

1. 定义

1.1 上下文协议

__enter__()和__exit__()方法构成了上下文协议

1.2 上下文管理器

实现了上下文协议的对象,即为上下文管理器

2. 优点

  1. 提高代码的复用率
  2. 提高代码的优雅度
  3. 提高代码的可读性

3. 使用说明

3.1 __enter__(self)说明

若存在类方法, 则__enter__()方法需要返回self, 否则无法调用对应方法

# __enter__()返回self
class Demo(object):

    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __enter__(self):
        print("-----enter.-----")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("-----exit.-----")

    def division(self):
        return self.a / self.b

if __name__ == '__main__':

    with Demo(1, 1) as test:
        print(test.division())
   
# 执行结果     
>>> -----enter.-----
>>> 1.0
>>> -----exit.-----
# __enter__()不返回self
class Demo(object):

    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __enter__(self):
        print("-----enter.-----")
        # return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("-----exit.-----")

    def division(self):
        return self.a / self.b

if __name__ == '__main__':

    with Demo(1, 1) as test:  # 由于未返回self, 则test实际是空对象
        print(test.division())
   
# 执行结果     
>>> -----enter.-----
>>> -----exit.-----
>>> Traceback (most recent call last):
>>>   File "E:/StudyPython/study_context_manager/test.py", line 28, in 
>>>     print(test.division())
>>> AttributeError: 'NoneType' object has no attribute 'division'

3.2 __exit__(self, exc_type, exc_val, exc_tb)方法说明

  • __exit__()方法默认返回False.
  • 若__exit__()方法返回False, 则在管理器执行过程中发生的异常将被抛出
  • 若__exit__()方法返回True, 则在管理器执行过程中发生的异常不被抛出
  • exc_type: 异常类型, 若无异常信息, 值为None
  • exc_val: 异常信息, 若无异常信息, 值为None
  • exc_tb: 异常堆栈, 若无异常信息, 值为None

    PS: 若__exit__()方法返回True, 但是__enter__()和__exit__()方法中存在异常, 异常也会被抛出
# __exit__()方法默认返回值示例
class Demo(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __enter__(self):
        print("-----enter.-----")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("-----exit.-----")  # 默认返回False, 异常将被抛出

    def division(self):
        return self.a / self.b


if __name__ == "__main__":
    with Demo(1, 0) as test:
        print(test.division())

# 执行结果
>>> -----enter.-----
>>> -----exit.-----
>>> Traceback (most recent call last):
>>>   File "E:/StudyPython/study_context_manager/test.py", line 28, in 
>>>     print(test.division())
>>>   File "E:/StudyPython/study_context_manager/test.py", line 23, in division
>>>     return self.a / self.b
>>> ZeroDivisionError: division by zero
# __exit__()方法返回False示例
class Demo(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __enter__(self):
        print("-----enter.-----")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("-----exit.-----")
        return False  # 返回False, 异常将被抛出

    def division(self):
        return self.a / self.b


if __name__ == "__main__":
    with Demo(1, 0) as test:
        print(test.division())

# 执行结果
>>> -----enter.-----
>>> -----exit.-----
>>> Traceback (most recent call last):
>>>   File "E:/StudyPython/study_context_manager/test.py", line 28, in 
>>>     print(test.division())
>>>   File "E:/StudyPython/study_context_manager/test.py", line 23, in division
>>>     return self.a / self.b
>>> ZeroDivisionError: division by zero
# __exit__()方法返回True示例
class Demo(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __enter__(self):
        print("-----enter.-----")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("-----exit.-----")
        return True  # 返回True, 异常不被抛出

    def division(self):
        return self.a / self.b


if __name__ == "__main__":
    with Demo(1, 0) as test:
        print(test.division())

# 执行结果
>>> -----enter.-----
>>> -----exit.-----
# __exit__()方法参数说明示例
class Demo(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __enter__(self):
        print("-----enter.-----")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("-----exit.-----")
        print(exc_type)  # 异常类型
        print(exc_val)  # 异常信息
        print(exc_tb)  # 异常堆栈
        return True

    def division(self):
        return self.a / self.b


if __name__ == "__main__":
    with Demo(1, 0) as test:
        print(test.division())

# 执行结果
>>> -----enter.-----
>>> -----exit.-----
>>> 
>>> division by zero
>>> 
# __enter__()方法中存在异常示例
class Demo(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __enter__(self):
        print("-----enter.-----")
        raise ValueError("test.")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("-----exit.-----")
        print(exc_type)
        print(exc_val)
        print(exc_tb)
        return True

    def division(self):
        return self.a / self.b


if __name__ == "__main__":
    with Demo(1, 0) as test:
        print(test.division())
        
# 执行结果
>>> -----enter.-----
>>> Traceback (most recent call last):
>>>   File "E:/StudyPython/study_context_manager/test.py", line 32, in 
>>>     with Demo(1, 0) as test:
>>>   File "E:/StudyPython/study_context_manager/test.py", line 17, in __enter__
>>>     raise ValueError("test.")
>>> ValueError: test.
# __exit__()方法中存在异常示例
class Demo(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __enter__(self):
        print("-----enter.-----")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("-----exit.-----")
        print(exc_type)
        print(exc_val)
        print(exc_tb)
        raise ValueError("test.")  # 由于__exit__()存在异常, 上下文管理执行过程中的异常将被一并抛出
        return True

    def division(self):
        return self.a / self.b


if __name__ == "__main__":
    with Demo(1, 0) as test:
        print(test.division())
        
# 执行结果
>>> -----enter.-----
>>> -----exit.-----
>>> 
>>> division by zero
>>> 
>>> Traceback (most recent call last):
>>>   File "E:/StudyPython/study_context_manager/test.py", line 33, in 
>>>     print(test.division())
>>>   File "E:/StudyPython/study_context_manager/test.py", line 28, in division
>>>     return self.a / self.b
>>> ZeroDivisionError: division by zero
>>> 
>>> During handling of the above exception, another exception occurred:
>>> 
>>> Traceback (most recent call last):
>>>   File "E:/StudyPython/study_context_manager/test.py", line 33, in 
>>>     print(test.division())
>>>   File "E:/StudyPython/study_context_manager/test.py", line 24, in __exit__
>>>     raise ValueError("test.")
>>> ValueError: test.

4. 实现方式

  • 基于类实现
  • 基于装饰器实现

    PS: 上述所有示例, 全是基于类实现, 将不再做示例演示

4.1 基于装饰器实现

yield 关键字之前的部分等价于__enter__()方法, 之后的部分等价于__exit__()方法

# 不对yield进行try操作, 上下文管理中异常将被抛出, 且不能执行yield之后的代码
from contextlib import contextmanager

@contextmanager
def demo(a, b):
    print("-----enter.-----")
    c = a / b
    yield c
    print("-----exit.-----")

if __name__ == '__main__':
    with demo(1, 1) as c:
        print(c)
        raise ValueError(123)

# 执行结果        
>>> -----enter.-----
>>> 1.0
>>> Traceback (most recent call last):
>>>   File "E:/StudyPython/study_context_manager/test.py", line 32, in 
>>>     raise ValueError(123)
>>> ValueError: 123
# 对yield进行try操作, 且上下文管理中异常被抛出示例, 等价于__enter__()方法返回False
from contextlib import contextmanager

@contextmanager
def demo(a, b):
    print("-----enter.-----")
    c = a / b
    try:
        yield c
    except Exception as e:
        raise e
    finally:
        print("-----exit.-----")


if __name__ == "__main__":
    with demo(1, 1) as c:
        print(c)
        raise ValueError(123)

# 执行结果        
>>> -----enter.-----
>>> 1.0
>>> -----exit.-----
>>> Traceback (most recent call last):
>>>   File "E:/StudyPython/study_context_manager/test.py", line 38, in 
>>>     raise ValueError(123)
>>> ValueError: 123
# 对yield进行try操作, 且上下文管理中异常被抛出示例, 等价于__enter__()方法返回True
from contextlib import contextmanager

@contextmanager
def demo(a, b):
    print("-----enter.-----")
    c = a / b
    try:
        yield c
    except Exception as e:
        print(type(e))
        print(e)
    finally:
        print("-----exit.-----")


if __name__ == "__main__":
    with demo(1, 1) as c:
        print(c)
        raise ValueError(123)

# 执行结果        
>>> -----enter.-----
>>> 1.0
>>> 
>>> 123
>>> -----exit.-----

5. 其他用法

5.1 contextlib.closing()

# closing()代码
class closing(AbstractContextManager):
    """Context to automatically close something at the end of a block.

    Code like this:

        with closing(.open()) as f:
            

    is equivalent to this:

        f = .open()
        try:
            
        finally:
            f.close()

    """
    def __init__(self, thing):
        self.thing = thing
    def __enter__(self):
        return self.thing
    def __exit__(self, *exc_info):
        self.thing.close()

# 代码分析
closing类本身为基于类实现的上线文管理器, 在__exit__()方法中调用thing变量的close()方法. 所以thing变量必须要拥有close()方法.
但是__exit__()方法并没有return, 所以上线文管理器执行过程中的异常将被抛出. 若要抛出异常需要重写closing类中的__exit__()方法.
# 用法示例
from contextlib import closing

class Demo(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def division(self):
        return self.a / self.b

    def close(self):
        print("-----exit.-----")
        return True
if __name__ == '__main__':
    with closing(Demo(1, 1)) as test:
        print(test.division())
        
# 执行结果
>>> 1.0
>>> -----exit.-----
# 抛出异常的情况
from contextlib import closing

class Demo(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def division(self):
        return self.a / self.b

    def close(self):
        print("-----exit.-----")
        return True
if __name__ == '__main__':
    with closing(Demo(1, 0)) as test:
        print(test.division())

# 执行结果        
>> -----exit.-----
>> Traceback (most recent call last):
>>   File "E:/StudyPython/study_context_manager/test.py", line 24, in 
>>     print(test.division())
>>   File "E:/StudyPython/study_context_manager/test.py", line 17, in division
>>     return self.a / self.b
>> ZeroDivisionError: division by zero
# 重写closing类中的__exit__()方法
from contextlib import closing

class MyClosing(closing):
    def __exit__(self, exc_type, exc_val, exc_tb):
        return self.thing.close()

class Demo(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def division(self):
        return self.a / self.b

    def close(self):
        print("-----exit.-----")
        return True
if __name__ == '__main__':
    with MyClosing(Demo(1, 0)) as test:
        print(test.division())
        
# 执行结果        
>> -----exit.-----

你可能感兴趣的:(上下文管理器)