源码剖析@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()")
源码剖析@contextlib.contextmanager的更多相关文章
- 04: 打开tornado源码剖析处理过程
目录:Tornado其他篇 01: tornado基础篇 02: tornado进阶篇 03: 自定义异步非阻塞tornado框架 04: 打开tornado源码剖析处理过程 目录: 1.1 torn ...
- jQuery之Deferred源码剖析
一.前言 大约在夏季,我们谈过ES6的Promise(详见here),其实在ES6前jQuery早就有了Promise,也就是我们所知道的Deferred对象,宗旨当然也和ES6的Promise一样, ...
- Nodejs事件引擎libuv源码剖析之:高效线程池(threadpool)的实现
声明:本文为原创博文,转载请注明出处. Nodejs编程是全异步的,这就意味着我们不必每次都阻塞等待该次操作的结果,而事件完成(就绪)时会主动回调通知我们.在网络编程中,一般都是基于Reactor线程 ...
- Apache Spark源码剖析
Apache Spark源码剖析(全面系统介绍Spark源码,提供分析源码的实用技巧和合理的阅读顺序,充分了解Spark的设计思想和运行机理) 许鹏 著 ISBN 978-7-121-25420- ...
- 基于mybatis-generator-core 1.3.5项目的修订版以及源码剖析
项目简单说明 mybatis-generator,是根据数据库表.字段反向生成实体类等代码文件.我在国庆时候,没事剖析了mybatis-generator-core源码,写了相当详细的中文注释,可以去 ...
- STL"源码"剖析-重点知识总结
STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...
- SpringMVC源码剖析(四)- DispatcherServlet请求转发的实现
SpringMVC完成初始化流程之后,就进入Servlet标准生命周期的第二个阶段,即“service”阶段.在“service”阶段中,每一次Http请求到来,容器都会启动一个请求线程,通过serv ...
- 自己实现多线程的socket,socketserver源码剖析
1,IO多路复用 三种多路复用的机制:select.poll.epoll 用的多的两个:select和epoll 简单的说就是:1,select和poll所有平台都支持,epoll只有linux支持2 ...
- Java多线程9:ThreadLocal源码剖析
ThreadLocal源码剖析 ThreadLocal其实比较简单,因为类里就三个public方法:set(T value).get().remove().先剖析源码清楚地知道ThreadLocal是 ...
随机推荐
- Maven本地仓库/中央仓库(阿里镜像)/JDK配置
第一步打开你已经下载好的Maven,进入:根磁盘:\......\apache-maven-3.6.2\conf文件夹找到settings.xml文件(没有的可以自行创建一个同名的文件即可) 使用文本 ...
- CVE-2020-0796永恒之黑复现POC EXP以及修复方案
描述: 北京时间3月12日,针对最新披露的SMB远程代码执行漏洞(CVE-2020-0796),微软官方发布了针对Windows 10/Server禁用SMBv3(SMB 3.1.1版本)协议压缩的安 ...
- Java实现 洛谷 P1090 合并果子
import java.io.BufferedInputStream; import java.util.Arrays; import java.util.Scanner; public class ...
- java实现第四届蓝桥杯猜年龄
猜年龄 题目描述 美国数学家维纳(N.Wiener)智力早熟,11岁就上了大学.他曾在1935~1936年应邀来中国清华大学讲学. 一次,他参加某个重要会议,年轻的脸孔引人注目.于是有人询问他的年龄, ...
- Python学习之求阶乘篇
描述 给定一个数n,范围为0≤n≤100,请你编程精确的求出n的阶乘n!. 输入 输入数据有多行,每行一个整数n,当n<0时输入结束. 输出 输出n的阶乘. 样例输入 1234-1 样例输出 1 ...
- kibana的Dev Tool中如何对es进行增删改查
kinaba Dev Tool中对es(elasticSearch)进行增删改查 一.查询操作 查询语句基本语法 以下语句类似于mysql的: select * from xxx.yyy.topic ...
- 【String注解驱动开发】如何按照条件向Spring容器中注册bean?这次我懂了!!
写在前面 当bean是单实例,并且没有设置懒加载时,Spring容器启动时,就会实例化bean,并将bean注册到IOC容器中,以后每次从IOC容器中获取bean时,直接返回IOC容器中的bean,不 ...
- javascript 面向对象学习(三)——this,bind、apply 和 call
this 是 js 里绕不开的话题,也是非常容易混淆的概念,今天试着把它理一理. this 在非严格模式下,总是指向一个对象,在严格模式下可以是任意值,本文仅考虑非严格模式.记住它总是指向一个对象对于 ...
- 说出 Servlet 的生命周期,并说出 Servlet 和 CGI 的区别。
Servlet 被服务器实例化后,容器运行其 init 方法,请求到达时运行其 service 方法,service 方法自动派 遣运行与请求对应的 doXXX 方法(doGet,doPost)等,当 ...
- (二)JPA实体类主键生成策略
在JPA中,配置实体类的主键的生成策略使用 @GeneratedValue @Id @Column(name = "id") @GeneratedValue(strategy = ...