With语句以及@contextmanager的语法解析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了With语句以及@contextmanager的语法解析相关的知识,希望对你有一定的参考价值。

with 语句以及@contextmanager的语法解析

?

with语句可以通过很简单的方式来替try/finally语句。 with语句中EXPR部分必须是一个包含__enter__()__exit__()方法的对象,也就是Context Manager。使用with语句的目的:

  • 提供可靠的资源自动释放,在with代码执行前请求资源,代码运行结束后资源会释放。
  • 简化代码,代码可读性以及逻辑的简明都会提高很多。
  • 创造临时的上下文环境,例如做一个临时的网络请求并获取返回值作为上下文环境。
  • 通过contextmanager和generator创造线程操作异步所。

下述例子描述with...as语句的实现原理:

In?[15]:
?
?
#EXPR open(‘var/log/test.log)
EXPR = open(‘/var/log/test.log‘)
with EXPR as VAR:
    #BLOCK START
    data = VAR.read(50)
    print data
    #BLOCK END
?
?
?
test1
test2
test3
test4
test5

?

实现原理:

  1. 在with语句中, EXPR必须是一个包含__enter__()__exit__()方法的对象(Context Manager)。
  2. 调用EXPR的__enter__()方法申请资源并将结果赋值给VAR变量。
  3. 通过try/except确保代码块BLOCK正确调用,否则调用EXPR的__exit__()方法退出并释放资源。
  4. 在代码块BLOCK正确执行后,最终执行EXPR的__exit__()方法释放资源。
In?[16]:
?
?
import sys
EXPR = open(‘/var/log/test.log‘)
EXIT = type(EXPR).__exit__ #not calling it yet
VAR = type(EXPR).__enter__(EXPR)
EXC = True
try:
    try:
        #BLOCK START
        data = VAR.read(50)
        print data
        #BLOCK END
    except:
        EXC = False
        if not EXIT(EXPR, *sys.exc_info()):
            raise
finally:
    if EXC:
        EXIT(EXPR, None, None, None)
?
?
?
test1
test2
test3
test4
test5

?

EXPR可以使用with语句的前提,必须是一个包含__enter__()__exit()__方法的对象(Context Manager),最直接的方式是声明一个对象,在__enter__()方法里面申请资源,在__exit__()方法里面释放资源;EXPR返回此对象。

更通用和更高效的将普通的函数转变为包含__enter__()__exit__()方法的对象的方法是:通过一个特定的decorator(@contextmanager)扩展该函数并将函数声明为非循环的单一返回值generator

?

generator可以将函数变成类似于iterator,每次调用好像通过iterator的next逐步读取,而不是一次返回。

  • 比实现一个iterator简单,iterator需要实现__init__,__iter__,__next__函数。
  • 比将结果一次返回(全部读取到内存中)要节省内存,通过next可以逐步获取需要的值。
?

@contextmanagerdecorator的实现原理:

  1. 声明contextmanager的decorator函数。参数是generator,返回值是一个接受和generator函数同样参数并且将generaor函数和参数传递到Context Manager构造函数并返回Context Manager对象的函数。绕死了!!!

    decorator函数,是接受函数作为参数,并且返回一个函数的的函数。当对函数func进行此修饰时相当于对func进行一次转变: func = decorator(func),在这里generator被修饰后变成了help(*args, **kwargs)函数

  2. __enter__()方法中使用generator的next()方法获取第一个返回值。如果gen并不是generator函数,抛出一个runtime异常。

  3. __exit__()方法中,如果存在异常将异常跑出,否则继续调用generator的next()方法。因此generator是一个具有唯一值非loop的generator因此会抛出stopiteration异常(正常预期值),否则抛出一个runtime异常。
In?[24]:
?
?
 class GeneratorContextManager(object):
?
    def __init__(self, gen):
        self.gen = gen
?
    def __enter__(self):
        try:
            return self.gen.next()
        except StopIteration:
            raise RuntimeError("generator didn‘t yield")
?
    def __exit__(self, type, value, traceback):
        if type is None:
            try:
                self.gen.next()
            except StopIteration:
                return
            else:
                raise RuntimeError("generator didn‘t stop")
        else:
            try:
                self.gen.throw(type, value, traceback)
                raise RuntimeError("generator didn‘t stop after throw()")
            except StopIteration:
                return True
            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.以上是关于With语句以及@contextmanager的语法解析的主要内容,如果未能解决你的问题,请参考以下文章

Python教程:with语句的用法

Python中with用法详解

Python中with用法详解

如何用python 中with 用法

Python核心技术与实战——二一|巧用上下文管理器和with语句精简代码

Python3中with用法