美文网首页
上下文管理器

上下文管理器

作者: testerH2o | 来源:发表于2020-06-16 17:59 被阅读0次

    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 <module>
    >>>     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 <module>
    >>>     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 <module>
    >>>     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.-----
    >>> <class 'ZeroDivisionError'>
    >>> division by zero
    >>> <traceback object at 0x000002D384CD9808>
    
    # __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 <module>
    >>>     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.-----
    >>> <class 'ZeroDivisionError'>
    >>> division by zero
    >>> <traceback object at 0x0000020EC598A8C8>
    >>> Traceback (most recent call last):
    >>>   File "E:/StudyPython/study_context_manager/test.py", line 33, in <module>
    >>>     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 <module>
    >>>     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 <module>
    >>>     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 <module>
    >>>     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
    >>> <class 'ValueError'>
    >>> 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(<module>.open(<arguments>)) as f:
                <block>
    
        is equivalent to this:
    
            f = <module>.open(<arguments>)
            try:
                <block>
            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 <module>
    >>     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.-----
    

    相关文章

      网友评论

          本文标题:上下文管理器

          本文链接:https://www.haomeiwen.com/subject/iovrxktx.html