前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python的上下文管理器

python的上下文管理器

作者头像
哒呵呵
发布2018-08-06 14:59:10
4590
发布2018-08-06 14:59:10
举报

上下文管理器其实是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)抛出异常。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-08-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 鸿的学习笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档