专栏首页鸿的学习笔记python的上下文管理器

python的上下文管理器

上下文管理器其实是with语句,这是为了简化try/finally模式,这可以保证一段代码在运行完之后,即使出现错误也能正确的运行。finally的语句用于释放重要资源,比如数据库和文件

的句柄,或者还原临时变更的对象,例如锁。

就跟前面的系列文章所述,上下文管理器也是一种协议,包含__enter__和__exit__方法。在with语句开始运行是会调用__enter__方法,结束后会调用__exit__方法。最常见的例子就是打开文件。

打开所在项目的文件

with open('List.py',encoding = 'utf8') as f:

src = f.read()

f

Out[4]: <_io.TextIOWrapper name='List.py' mode='r' encoding='utf8'>

f.closed, f.encoding

Out[5]: (True, 'utf8')

我们可以看出f这个变量依然可以用,但是文件句柄已经关闭了。as语句只不过是把值绑定到了目标变量,as语句是可选的,但是如果是打开文件或者是连接数据库,则必须绑定获得句柄。

说了这么多,这意味着我们也可以制造一个上下文管理器,只要实现了__enter__和__exit__方法。

class test():

def __enter__(self):

print('enter')

return 'enter'

def __exit__(*args):

print(args)

return 'exit'

with test() as f:

print('sdad')

raise Zero("dsad")

显示出来的结果如下:

enter

sdad

(<__main__.test object at 0x0000015C1CE351D0>, <class 'NameError'>, NameError("name 'Zero' is not defined",), <traceback object at 0x0000015C1CE27C08>)

再看看f变量:

f

Out[3]: 'enter'

我们自己定义了一个类,里面包含了__enter__和__exit__方法,在with语句开始时,你会发现显示屏上出现了'enter',这印证了之前的话,__enter__会在with语句刚开始时执行,并且

把return的结果反馈回f变量。进入到语句块之后,会显示出'sdad',知道报错,然后你会发现__exit__里面接受了四个变量,分别是:

--self:本身的实例

--exc_type:异常类(比如NameError)

--exc_value:异常的实例,可以通过exc_value.args获取参数

--traceback:traceback对象,Extract, format and print information about Python stack traces.也就是异常栈

更详细的信息在:https://docs.python.org/3.5/library/stdtypes.html#typecontextmanager

其实每次这样定义会很累,官方提供了contextlib模块来简化开发。

里面一共包含了:

__all__ = ["contextmanager", "closing", "ContextDecorator", "ExitStack",

"redirect_stdout", "redirect_stderr", "suppress"]

其中,contextmanager可以把简单的生成器函数变成上下文管理器。这个也是用了yield语句,但是这个却与迭代无关。

官网给出的例子如下:

from contextlib import contextmanager

@contextmanager

def tag(name):

print("<%s>" % name)

yield

print("</%s>" % name)

with tag('woshi'):

print('love')

<woshi>

love

</woshi>

结果如上通过yield语句将函数的定义体分成了两部分,yield前面的语句执行__enter__,后面的语句执行__exit__,我们可以改变test类

@contextmanager

def text():

print('enter')

yield 'enter'

print('exit')

with text() as f:

print('sd')

enter

sd

exit

class _GeneratorContextManager(ContextDecorator):

"""Helper for @contextmanager decorator."""

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)

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.

def _recreate_cm(self):

# _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):

try:

return next(self.gen)

except StopIteration:

raise RuntimeError("generator didn't yield") from None

def __exit__(self, type, value, traceback):

if type is None:

try:

next(self.gen)

except StopIteration:

return

else:

raise RuntimeError("generator didn't stop")

else:

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)

raise RuntimeError("generator didn't stop after throw()")

except StopIteration as exc:

# 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:

# Likewise, avoid suppressing if a StopIteration exception

# was passed to throw() and later wrapped into a RuntimeError

# (see PEP 479).

if 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.

#

if sys.exc_info()[1] is not value:

raise

从源码可以看出:

@contextmanager 实际上是把函数包装成了一个类,

__enter__:调用生成器函数,保存生成器对象,如上所述是,next(self.gen),调用到yield关键字所在的位置,返回这个值绑定到as后面的变量。

__exit__:检查有没有异常传给exc_type,没有的话继续调用之后的代码,否则调用self.gen.throw(type, value, traceback)抛出异常。

本文分享自微信公众号 - 鸿的学习笔记(shujuxuexizhilu),作者:鸿

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2017-08-17

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 线性表之顺序存储结构

    线性表的数据对象集合为{a1,a2,...an},每个元素的数据类型均为DataType。

    哒呵呵
  • DBDB: 一个简单的key/value数据库(三)

    前文点击链接:DBDB: 一个简单的key/value数据库(一) 前文点击链接:DBDB: 一个简单的key/value数据库(二)

    哒呵呵
  • Python写的Python解释器(七)--完结篇

    在程序运行时,只会创建一次VirtualMachine实例,这是因为只有一个Python解释器。 VirtualMachine存储着call stack,异常状...

    哒呵呵
  • 机器学习(7)之感知机python实现

    关键字全网搜索最新排名 【机器学习算法】:排名第一 【机器学习】:排名第二 【Python】:排名第三 【算法】:排名第四 感知器PLA是一种最简单,最基本的线...

    昱良
  • N元分词算法

    unigram 一元分词,把句子分成一个一个的汉字 bigram 二元分词,把句子从头到尾每两个字组成一个词语 trigram 三元分词,把句子从头到尾每三...

    学到老
  • N元分词算法

    在自然语言处理中,我们经常需要用到n元语法模型。 其中,有关中文分词的一些概念是我们需要掌握的,譬如: unigram 一元分词,把句子分成一个一个的汉字 b...

    学到老
  • 接口测试 | 24 requests + unittest集成你的接口测试

    概述 本文就如何结合requests、unittest进行实例演示,如果你还不了解unittest、PO模型,请翻阅公众号前期发布的unittest专题和PO模...

    苦叶子
  • 接口测试 | 24 requests + unittest集成你的接口测试

    概述 本文就如何结合requests、unittest进行实例演示,如果你还不了解unittest、PO模型,请翻阅公众号前期发布的unittest专题和PO模...

    苦叶子
  • ReactiveCocoa使用心得

    5.NSMutableArray 因为NSMutableArray不支持KVO,所以用另外一个方式处理:

    freesan44
  • 利用Python让你的命令行像坤坤一样会打篮球

    承接上文,作为一个经常逛b站的肥宅,近期b站上除了流行"品如”素材的视频,更多的莫过于蔡xx打球视频的了,有模仿的,有对比的,有手绘的,更过分的是竟然有人在命令...

    统计学家

扫码关注云+社区

领取腾讯云代金券