标签: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