源码剖析@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是 ...
随机推荐
- 跨域解决方案 - webpack devServer
1. 定义 如果一个项目中配置了webpack, 那么我们使用 webpack devServer 来配置代理转发请求来达到解决跨域问题的目的 webpack devServer 能够解决跨域问题的根 ...
- ActiveMQ 笔记(二)部署和DEMO(队列、主题)
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 一.部署操作 1. 部署在linux 上的acvtiveMQ 要可以通过前台windows 的页面访问, ...
- Java实现 蓝桥杯算法提高金明的预算方案
题目描述 金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间.更让他高兴的是,妈妈昨天对他说:"你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过NN元 ...
- Java中数组二分法查找
算法:当数组的数据量很大适宜采用该方法.采用二分法查找时,数据需是有序不重复的,如果是无序的也可通过选择排序.冒泡排序等数组排序方法进行排序之后,就可以使用二分法查找. 基本思想:假设数据是按升序排序 ...
- 第六届蓝桥杯JavaB组省赛真题
解题代码部分来自网友,如果有不对的地方,欢迎各位大佬评论 题目1.三角形面积 题目描述 如图1所示.图中的所有小方格面积都是1. 那么,图中的三角形面积应该是多少呢? 请填写三角形的面积.不要填写任何 ...
- Java实现LeetCode_0041_FirstMissingPositive
package javaLeetCode.hard; import java.util.Arrays; public class FirstMissingPositive_41 { public st ...
- java实现蔬菜价格计算
** 蔬菜价格计算** 计算蔬菜总价 为了丰富群众菜篮子,平抑菜价,相关部分组织了蔬菜的调运.今某箱中有多个品种的蔬菜.蔬菜的单价(元/公斤)存放在price数组中,蔬菜的重量(公斤)存放在weigh ...
- JPA入门及深入
一:ORM介绍 ORM(Object-Relational Mapping) 表示对象关系映射.在面向对象的软件开发中,通过ORM,就可以把对象映射到关系型数据库中.只要有一套程序能够做到建立对象与数 ...
- @Ajax.ActionLink跳转页面的问题解决方案 MVC Ajax不支持问题
[JavaScriptResult]在客户端执行服务器返回的JavaScript代码当一个内置的Ajax辅助方法请求一个操作方法,该方法会返回一个在客户端执行立即的脚本. public ActionR ...
- 树莓派配置文档config.txt说明
原文连接:http://elinux.org/RPi_config.txt 由于树莓派并没有传统意义上的BIOS, 所以现在各种系统配置参数通常被存在"config.txt"这个文 ...