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()")
    
  • 相关阅读:
    2012 regional 。。
    joj2443
    java笔记代码实现汉诺塔移动过程和移动次数
    java笔记增加虚拟机内存
    java笔记修改javadoc为中文API信息
    java笔记String类格式化当天日期转换符文档
    java笔记正则表达式的运用(包括电话,邮箱验证等)
    java笔记String类对象解析与运用
    Java笔记IO流的运用
    java笔记BigDecimal的使用
  • 原文地址:https://www.cnblogs.com/EmptyRabbit/p/13207334.html
Copyright © 2011-2022 走看看