码迷,mamicode.com
首页 > 其他好文 > 详细

源码剖析@contextlib.contextmanager

时间:2020-06-28 00:31:58      阅读:64      评论:0      收藏:0      [点我收藏+]

标签:contex   org   __call__   none   protoc   cti   extman   must   处理异常   

#### 示例
```
@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()")
```

 

源码剖析@contextlib.contextmanager

标签:contex   org   __call__   none   protoc   cti   extman   must   处理异常   

原文地址:https://www.cnblogs.com/EmptyRabbit/p/13200541.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!