python中上下文管理器的理解以及应用场景

在学习到上下文管理器这个陌生的词之前,我学过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这个用法没怎么特别理解它的好处。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

如梦@_@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值