示例

@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的更多相关文章

  1. 04: 打开tornado源码剖析处理过程

    目录:Tornado其他篇 01: tornado基础篇 02: tornado进阶篇 03: 自定义异步非阻塞tornado框架 04: 打开tornado源码剖析处理过程 目录: 1.1 torn ...

  2. jQuery之Deferred源码剖析

    一.前言 大约在夏季,我们谈过ES6的Promise(详见here),其实在ES6前jQuery早就有了Promise,也就是我们所知道的Deferred对象,宗旨当然也和ES6的Promise一样, ...

  3. Nodejs事件引擎libuv源码剖析之:高效线程池(threadpool)的实现

    声明:本文为原创博文,转载请注明出处. Nodejs编程是全异步的,这就意味着我们不必每次都阻塞等待该次操作的结果,而事件完成(就绪)时会主动回调通知我们.在网络编程中,一般都是基于Reactor线程 ...

  4. Apache Spark源码剖析

    Apache Spark源码剖析(全面系统介绍Spark源码,提供分析源码的实用技巧和合理的阅读顺序,充分了解Spark的设计思想和运行机理) 许鹏 著   ISBN 978-7-121-25420- ...

  5. 基于mybatis-generator-core 1.3.5项目的修订版以及源码剖析

    项目简单说明 mybatis-generator,是根据数据库表.字段反向生成实体类等代码文件.我在国庆时候,没事剖析了mybatis-generator-core源码,写了相当详细的中文注释,可以去 ...

  6. STL"源码"剖析-重点知识总结

    STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...

  7. SpringMVC源码剖析(四)- DispatcherServlet请求转发的实现

    SpringMVC完成初始化流程之后,就进入Servlet标准生命周期的第二个阶段,即“service”阶段.在“service”阶段中,每一次Http请求到来,容器都会启动一个请求线程,通过serv ...

  8. 自己实现多线程的socket,socketserver源码剖析

    1,IO多路复用 三种多路复用的机制:select.poll.epoll 用的多的两个:select和epoll 简单的说就是:1,select和poll所有平台都支持,epoll只有linux支持2 ...

  9. Java多线程9:ThreadLocal源码剖析

    ThreadLocal源码剖析 ThreadLocal其实比较简单,因为类里就三个public方法:set(T value).get().remove().先剖析源码清楚地知道ThreadLocal是 ...

随机推荐

  1. Chisel3 - util - Arbiter

    https://mp.weixin.qq.com/s/7Y23gV6yPvtmvKHTo2I8mw   基于ReadyValid接口实现的多入单出仲裁器.   参考链接: https://github ...

  2. chrome和Firefox浏览器渲染页面的不同

    一直很好奇chrome和firefox这两大浏览器的页面渲染有什么不同,今天自己写了些html代码来做了下检验. 先做html编码,代码如下: <!DOCTYPE html><htm ...

  3. JAVA-蓝桥杯-算法训练-字符串变换

    问题描述 相信经过这个学期的编程训练,大家对于字符串的操作已经掌握的相当熟练了.今天,徐老师想测试一下大家对于字符串操作的掌握情况.徐老师自己定义了1,2,3,4,5这5个参数分别指代不同的5种字符串 ...

  4. Java实现 蓝桥杯 算法训练 Balloons in a Box

    试题 算法训练 Balloons in a Box 问题描述 你要写一个程序,使得能够模拟在长方体的盒子里放置球形的气球. 接下来是模拟的方案.假设你已知一个长方体的盒子和一个点集.每一个点代表一个可 ...

  5. Java实现 蓝桥杯 算法训练 Bit Compressor(暴力)

    试题 算法训练 Bit Compressor 问题描述 数据压缩的目的是为了减少存储和交换数据时出现的冗余.这增加了有效数据的比重并提高了传输速率.有一种压缩二进制串的方法是这样的: 将连续的n个1替 ...

  6. Java实现 LeetCode 155 最小栈

    155. 最小栈 设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈. push(x) – 将元素 x 推入栈中. pop() – 删除栈顶的元素. top() – 获取 ...

  7. Java实现 LeetCode 22 括号生成

    22. 括号生成 给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合. 例如,给出 n = 3,生成结果为: [ "((()))", &quo ...

  8. IDEA,PyCharm系列软件常用快捷键

    首先介绍一下小编常用的快捷键: 注释 ctrl+/ 当想看某个类或者某个方法的时候 ctrl+鼠标左键单击 运行程序 ctrl+shift+f10 调试程序 ctrl+shift+f9 撤销 ctrl ...

  9. java实现第三届蓝桥杯排日程

    排日程 [编程题](满分34分) 某保密单位机要人员 A,B,C,D,E 每周需要工作5天,休息2天. 上级要求每个人每周的工作日和休息日安排必须是固定的,不能在周间变更. 此外,由于工作需要,还有如 ...

  10. java实现第五届蓝桥杯切面条

    切面条 一根高筋拉面,中间切一刀,可以得到2根面条. 如果先对折1次,中间切一刀,可以得到3根面条. 如果连续对折2次,中间切一刀,可以得到5根面条. 那么,连续对折10次,中间切一刀,会得到多少面条 ...