示例

@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. 数据库之 MySQL --- 下载、安装 及 概述(一)

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一 . MySql数据库的安装 1.图解MySQL程序结构 ​ 2.双击运行安装程序:以Win32位为例 ...

  2. 【Java Spring Cloud 实战之路】- 使用Nacos和网关中心的创建

    0. 前言 在上一节中,我们创建了一个项目架构,后续的项目都会在那个架构上做补充. 1. Nacos 1.1 简介 Nacos可以用来发现.配置和管理微服务.提供了一组简单易用的特性集,可以快速实现动 ...

  3. 【Win10】BeyondCompare时提示"许可证密钥已被撤销"的解决办法

    删除...AppData\Roaming\Scooter Software\Beyond Compare 3目录下所有文件. 应该是对应了bcompare的配置文件以及记录文件.删除了之后,就等于新安 ...

  4. java实现 洛谷 P1540 机器

    import java.util.LinkedList; import java.util.Scanner; public class Main { private static Scanner ci ...

  5. java实现第五届蓝桥杯写日志

    写日志 写日志是程序的常见任务.现在要求在 t1.log, t2.log, t3.log 三个文件间轮流 写入日志.也就是说第一次写入t1.log,第二次写入t2.log,... 第四次仍然 写入t1 ...

  6. 洛谷 P1115 最大子序和

    **原题链接** ##题目描述   给出一段序列,选出其中连续且非空的一段使得这段和最大.     **解法**:       1.暴力枚举 时间:O(n^3)       2.简单优化 时间:O(n ...

  7. 用mvc框架查询数据库数据

    介绍下mvc框架,mvc框架一种软件设计典范,用一种业务逻辑.数据.界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑. 首先我们 ...

  8. Grafana 插件地图Worldmap不显示

    介绍 最近上了ELK 日志分析,想着手看下用户的分布情况,在kibana 中展示用户分布情况是没有问题的,但是索引添加到Granfana 中的话就无法展示. 问题描述 添加ES索引以后,map地图一片 ...

  9. SmokePing 快速搭建

    SmokePing介绍 smokeping是来监控IDC机房网络质量情况,可以从监控图上的延时与丢包情况分辨出机房的网络是否稳定,是否为多线,是否为BGP机房以及到各城市的三个运行商网络各是什么情况. ...

  10. 使用PD(Power Designer)设计数据库,并且生成可执行的SQL文件创建数据库(本文以SQL Server Management Studio软件执行为例)

    下载和安装PD: 分享我的软件资源,里面包含了对PD汉化包(链接出问题时可以留言,汉化包只能对软件里面部分菜单栏汉化) 链接:https://pan.baidu.com/s/1lNt1UGZhtDV8 ...