在学习到上下文管理器这个陌生的词之前,我学过with open() as f:这个读流的语句,当时只知道说用这个的话,可以自动读完文件的内容之后就关闭文件,不需要去调用close()方法了。
直到今天我在别人的代码中看到了with 的另外一种用法,后面跟的不是open,我就开始不理解了,超出了我的知识大纲了,于是乎就开始各种查资料,原来with是个和上下文管理器相关的东西。那么神马是上下文管理器呢?
听名字,上下文管理器,那就是由上文和下文,然后对什么进行管理呢?既然是python,那就是对代码进行管理呗。。
所以上下文管理器,其实就是对你的代码进行管理,在执行前必须做什么,执行后必须做什么。所以叫上下文。
比如你要看ppt课件,那是不是需要先打开ppt,然后就是正事了,你要看ppt,看完呢,你肯定要关闭ppt吧。
with之所以用在读流中,也是因为,读取一个文件流,那么前提是先打开文件,然后才能读取,最后是关闭。
那么懂了什么是上下文管理器,接下来就说说with,with和上下文管理器配套用的,with语句后跟的对象就是上下文管理器,它是一个类实例化后的对象,该类需要满足有两个方法,__enter__和__exit__这两个方法。比如下面的代码:
class prot(object):
def __init__(self):
print("对象初始化.....")
def __enter__(self):
print("我是上下文管理器的上文。")
def __exit__(self, exc_type, exc_val, exc_tb):
print("我是上下文管理器的下文。")
# return True
def add(self,a,b):
print(a+b)
g = prot()
with g as v:
g.add(1,2)
运行结果:
对象初始化.....
我是上下文管理器的上文。
3
我是上下文管理器的下文。
上面的g是一个对象,是上下文管理器,跟在with 语句后面,程序执行顺序是:
①遇到with 直接执行对象g对应的方法__enter__,并返回结果给v,不过with后面的as可以没有也行。这里需要注意一下,如果with后面直接跟with prot(),那么会先实例化一个对象,实例化的过程就是先开辟空间,__new__方法执行,执行之后,执行构造函数__init__进行属性初始化。对象创建之后,才会去执行__enter__方法。
②接着执行g.add()方法调用,执行不管有没有异常,都会去执行类的__exit__方法,如果有异常,会把异常的type , value , 错误栈信息传递给__exit__方法的三个参数,该方法可以通过print打印出来,同时异常信息也会返回。
如果在__exit__函数的结尾返回return True,那么就不会处理异常,异常信息也不会返回,看似什么错误也没有。
注意:
上面的__exit__函数必须要有三个参数接受异常的type , value , 错误栈信息,没有异常就返回None,有异常就返回,你可以选择不处理这几个值,那就是使用return True.。
应用场景:
我理解的就是需要有头有尾的一些场景,比如读取文件,读取数据库的内容,都是需要先打开,读取,然后关闭。
另外上下文管理器对于异常的处理也是很不错。
上面的上下文管理器跟装饰器是不是有点像,装饰器是对函数的装饰,而上下文管理器是对代码块进行装饰,且用到了装饰器contextmanager。
注意:使用with前提是with后面跟的是上下文管理器,with后面跟的对象对应的类定义了__enter__和__exit__方法才可以,否则会报错。
下面来介绍python内置的contextlib模块的用法:
查看contextlib的源码,定义了一些类和函数,这里要讲三个类和一个函数。
第一个是contextmanager函数,也是装饰器:
def contextmanager(func):
"""@contextmanager decorator.
Typical usage:
@contextmanager
def some_generator(<arguments>):
<setup>
try:
yield <value>
finally:
<cleanup>
This makes this:
with some_generator(<arguments>) as <variable>:
<body>
equivalent to this:
<setup>
try:
<variable> = <value>
<body>
finally:
<cleanup>
"""
@wraps(func)
def helper(*args, **kwds):
return _GeneratorContextManager(func, args, kwds)
return helper
和这个函数搭配使用的两个类一个是:_GeneratorContextManager,还有一个是这个类的父类,_GeneratorContextManagerBase,前者定义了__enter__和__exit__方法,后者是仅仅定义了对象的初始化方法,即构造函数,在contextmanager函数返回这个类的实例化对象时,会去调用这个子类的初始化方法,包括对待装饰的函数以及传入的参数进行初始化。
还有一个类:closing,也是定义了__enter__和__exit__方法,说明它也是一个上下文管理器,可以用在with后面。
第一个装饰器函数的用法:
from contextlib import contextmanager,closing
@contextmanager
def wrapped():
#类似__enter__
print("我是__enter__")
yield 1
#__exit__
print("我是__exit__")
with wrapped() as value:
print("我才是主要的内容")
print(value)
上面的代码,从上到下:首先是定义了一个函数wrapped,函数内部有yield关键字,看到这个就知道这个函数不是普通的函数,它是生成器函数,函数返回值是一个生成器,关于生成器不懂的话,可以翻我之前的文章,有专门介绍过。
回到正题,定义了一个生成器函数,然后这个函数头部还被装饰器函数装饰了。
接着就是with 关键字定义的东东了,with后面跟着的是生成器函数的调用,看到这里,你是不是有疑惑,刚才不是说过,with后面跟着的对象必须是一个类实例化后的对象,该类需要定义__enter__和__eixt__这两个方法吗,我怎么没看到,生成器函数调用不是返回的是生成器吗,这不对啊。
别急,虽然with后面是生成器函数的调用,但是实际上这个调用做了两件事:
生成函数调用和contextmananger装饰器的装饰。
wrapped()-------》等价于 contextmanager(wrapped)()
拆开一点一点来看:contextmanager(wrapped) —》contextmanager是导入模块的一个函数,那么contextmanager(wrapped) 就是函数调用,上面源码中可以看到,调用这个函数返回值是内部函数helper的引用。
然后加上()之后,就是helper(),而helper函数调用返回的是一个对象,也就是说contextmanager(wrapped)()返回的是一个对象,再往前推,wrapped()返回的就是一个对象,这个对象的类是_GeneratorContextManager,查看这个类的定义可以看到如下:
class _GeneratorContextManager(_GeneratorContextManagerBase,
AbstractContextManager,
ContextDecorator):
"""Helper for @contextmanager decorator."""
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):
# 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):
if type is None:
try:
next(self.gen)
except StopIteration:
return False
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)
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:
# Don't re-raise the passed in exception. (issue27122)
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).
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
raise RuntimeError("generator didn't stop after throw()")
上面的代码重点是__enter__和__exit__这两个方法,所以with后面能直接调用生成器函数,原因就是这个。
再看下,__enter__和__exit__方法中内容,可以看到有对生成器函数的next()取值。这个也就是为什么我的wrapped函数中需要yield关键字了,也就是说,如果你想用contextmanager作为自己函数的装饰器,那么就得是生成器函数才可以。
接下来,来看下with 语句代码是怎么个执行步骤:
首先:wrapped()函数的调用会去调用__enter__方法,这个方法中有一个next(wrapped()),所以会去调用我的生成器函数,就是打印我一个print,然后遇到yield就暂停,并把值返回给next(wrapped()),然后__enter__方法中也是return这个值,那么这个值就返回来给了value。
然后:就是执行正文了。
最后:执行结束之后,会去调用__exit__方法,这个方法也有next(wrapped())所以还会再去调用我的wrapped函数,是从上一次调用的位置接着执行后面的代码,所以会打印第二个print的内容,由于没有遇到yield,遇到了return,就返回了。
整个代码执行结束。
第二个closing类的用法:
class closing(AbstractContextManager):
"""Context to automatically close something at the end of a block.
Code like this:
with closing(<module>.open(<arguments>)) as f:
<block>
is equivalent to this:
f = <module>.open(<arguments>)
try:
<block>
finally:
f.close()
"""
def __init__(self, thing):
self.thing = thing
def __enter__(self):
return self.thing
def __exit__(self, *exc_info):
self.thing.close()
上面是closing类的源码,重点是__enter__和__exit__这两个方法,这两个方法中都没有next(),但是可以看到__exit__方法中有一个close方法的调用,所以感觉能用到closing的场景,应该就是一个类中有close方法的,且执行完正文之后需要去关闭的,那么就可以定义一个类,如下代码:
class Door(object) :
def open(self) :
print 'Door is opened'
def close(self) :
print 'Door is closed'
with closing(Door()) as door :
door.open()
返回结果:
Door is opened
Door is closed
closing这个用法没怎么特别理解它的好处。。。。