zoukankan      html  css  js  c++  java
  • 源码剖析@contextlib.contextmanager

    示例

    @contextlib.contextmanager
    def result(a):
        print('before')
        yield
        print('after')
    

    外层装饰源码

    包装func函数,真实调用func()时,返回的为_GeneratorContextManager对象

    def contextmanager(func):
        @wraps(func)
        def helper(*args, **kwds):
            return _GeneratorContextManager(func, args, kwds)
        return helper
    

    _GeneratorContextManager对象

    该对象实现上下文管理器,继承父类_GeneratorContextManagerBase,抽象类AbstractContextManager,ContextDecorator

    父类_GeneratorContextManagerBase

    用来初始化某些_GeneratorContextManager对象需要的属性,如被包装的生成器对象,调用生成器时的参数,对象的doc文档

    class _GeneratorContextManagerBase:
        """Shared functionality for @contextmanager and @asynccontextmanager."""
    
        def __init__(self, func, args, kwds):
            self.gen = func(*args, **kwds)  """保存被装饰的生成器对象"""
            self.func, self.args, self.kwds = func, args, kwds
            # Issue 19330: ensure context manager instances have good docstrings
            doc = getattr(func, "__doc__", None)   """用生成器的doc作为实例的doc,如果没有,就用类自己的doc作为实例doc"""
            if doc is None:
                doc = type(self).__doc__
            self.__doc__ = doc
            # Unfortunately, this still doesn't provide good help output when
            # inspecting the created context manager instances, since pydoc
            # currently bypasses the instance docstring and shows the docstring
            # for the class instead.
            # See http://bugs.python.org/issue19404 for more details.
    

    抽象类AbstractContextManager

    定义类需要实现上下文管理方法

    定义判断是否为AbstractContextManager子类的方法AbstractContextManager,即如果一个类实现了__enter__ and__exit__, 没有继承定义判断是否为AbstractContextManager子类的方法AbstractContextManager,issubclass(classname,AbstractContextManager)也为真

    class AbstractContextManager(abc.ABC):
    
        """An abstract base class for context managers."""
    
        def __enter__(self):
            """Return `self` upon entering the runtime context."""
            return self
    
        @abc.abstractmethod
        def __exit__(self, exc_type, exc_value, traceback):
            """Raise any exception triggered within the runtime context."""
            return None
    
        @classmethod
        def __subclasshook__(cls, C):
            if cls is AbstractContextManager:
                return _collections_abc._check_methods(C, "__enter__", "__exit__")
            return NotImplemented
    

    装饰类ContextDecorator

    继承这个类,可以直接作ContextDecorator对象作为装饰器,为函数执行提供上下文管理(被contextmanager装饰的函数,可以作为装饰器 & with语句使用)

    class ContextDecorator(object):
        "A base class or mixin that enables context managers to work as decorators."
    
        def _recreate_cm(self): 
            """Return a recreated instance of self.
    
            Allows an otherwise one-shot context manager like
            _GeneratorContextManager to support use as
            a decorator via implicit recreation.
    
            This is a private interface just for _GeneratorContextManager.
            See issue #11647 for details.
            """
            return self
    
        def __call__(self, func):
            @wraps(func)
            def inner(*args, **kwds):
                with self._recreate_cm():
                    return func(*args, **kwds)
            return inner
    
    

    示例

    class MyContextManager(ContextDecorator):
        "Test MyContextManager."
    
        def __enter__(self):
            print('enter')
            return self
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            print('exit')
    
    @MyContextManager()
    def report(a):
        print(a, 'report function')
        return a
    
    执行report(1), 输出:
    eneter
    1 report function
    exit
    1
    

    _GeneratorContextManager类

    对象的使用:

    @contextlib.contextmanager
    def gcm_func(a):
        print('before')
        print('gcm_func', a)
        yield
        print('after')
    
    #使用方式1:gcm_func直接作为上下文管理器:
    with gcm_func(1):
        print('-- in with ---')
    输出:
    before
    gcm_func 1
    -- in with ---
    after
    
    #使用方式2: gcm_func作为函数的上下文管理
    @gcm_func(1)
    def with_func(a):
        print('-- in with ---')
    
    with_func(1)
    with_func(1) 
    """
    注意:ContextDecorator中__call__定义了每次调用with_func前,会调用_recreate_cm生成新的_GeneratorContextManager对象作为上下文管理器,所以这边可以调用2次
    否则在第一次with_func(1)就已经清空gcm_func生成器并删除with_func属性
    """
    输出:
    同方式1
    
    class _GeneratorContextManager(_GeneratorContextManagerBase,
                                   AbstractContextManager,
                                   ContextDecorator):
        """Helper for @contextmanager decorator."""
    
        def _recreate_cm(self):
            """
            _GeneratorContextManager实例上下文管理一次就无法再次调用
            如果_GeneratorContextManager实例用作装饰器,每次调用时需要重新生成实例
            """
            # _GCM instances are one-shot context managers, so the
            # CM must be recreated each time a decorated function is
            # called
            return self.__class__(self.func, self.args, self.kwds)
    
        def __enter__(self):
            """被装饰函数的参数只有在初始化实例时有用"""
            # do not keep args and kwds alive unnecessarily
            # they are only needed for recreation, which is not possible anymore
            del self.args, self.kwds, self.func
            try:
                return next(self.gen)
            except StopIteration:
                raise RuntimeError("generator didn't yield") from None
    
        def __exit__(self, type, value, traceback):
            """
            type: with语句内抛出的异常类
            value: with语句内抛出的异常信息
            traceback: with语句内抛出的异常堆栈
            """
            a=str(type)
    
            if type is None:
            """
            with语句内没有报错,往yield停止的部分继续执行,并会抛出异常
            如果往下走没有遇到stop异常,也就是contextmanager函数有两个yield,会报错"""
                try:
                    next(self.gen)
                except StopIteration:
                    return False
                else:
                    raise RuntimeError("generator didn't stop")
            else:
                """
                如果with中抛出了异常,在yield处抛出异常
                """
                if value is None:
                    # Need to force instantiation so we can reliably
                    # tell if we get the same exception back
                    value = type()
                try:
                    self.gen.throw(type, value, traceback)
                except StopIteration as exc:
                    """如果抛出的异常在yield中被except,不再抛出,而是往下走会抛出生成器的stop异常"""
                    # Suppress StopIteration *unless* it's the same exception that
                    # was passed to throw().  This prevents a StopIteration
                    # raised inside the "with" statement from being suppressed.
                    return exc is not value
                except RuntimeError as exc:
                    """如果with中有异常,抛出with中的异常给yield后,如果后续的语句有异常,判断异常是否为属于上下文管理器的异常"""
                    # Don't re-raise the passed in exception.(issue27122)
                    """如果是在上下文管理器中except raise异常,不要再抛出"""
                    if exc is value:   
                        return False
                    # Likewise, avoid suppressing if a StopIteration exception
                    # was passed to throw() and later wrapped into a RuntimeError
                    # (see PEP 479).
                    """忽略with中抛出的stop异常,不在这里抛出异常"""
                    if type is StopIteration and exc.__cause__ is value:
                        return False
                    """如果是后续语句中其他异常,属于上下文管理器的异常,抛出"""
                    raise
                except:
                    # only re-raise if it's *not* the exception that was
                    # passed to throw(), because __exit__() must not raise
                    # an exception unless __exit__() itself failed.  But throw()
                    # has to raise the exception to signal propagation, so this
                    # fixes the impedance mismatch between the throw() protocol
                    # and the __exit__() protocol.
                    #
                    # This cannot use 'except BaseException as exc' (as in the
                    # async implementation) to maintain compatibility with
                    # Python 2, where old-style class exceptions are not caught
                    # by 'except BaseException'.
                    if sys.exc_info()[1] is value:
                        return False
                    raise
                """处理异常之后,往下走还有yield"""
                raise RuntimeError("generator didn't stop after throw()")
    
  • 相关阅读:
    智能移动机器人背后蕴含的技术——激光雷达
    Kalman Filters
    Fiddler抓HttpClient的包
    VSCode开发WebApi EFCore的坑
    WPF之小米Logo超圆角的实现
    windows react打包发布
    jenkins in docker踩坑汇总
    Using ML.NET in Jupyter notebooks 在jupyter notebook中使用ML.NET ——No design time or full build available
    【Linux知识点】CentOS7 更换阿里云源
    【Golang 报错】exec gcc executable file not found in %PATH%
  • 原文地址:https://www.cnblogs.com/EmptyRabbit/p/13207334.html
Copyright © 2011-2022 走看看