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.
                #
                if sys.exc_info()[1] is not value:
                    raise
def contextmanager(func):
    def helper(*args, **kwds):
        return GeneratorContextManager(func(*args, **kwds))
    return helper
 
 
 

上述并没有涉及到资源的申请和释放,因此在generator函数里面,第一个loop即__enter__()函数调用时,进行资源的申请,第二个loop也就是__exit__()函数调用时抛出异常进入finally模块释放资源。下面是应用示例:

In [25]:
 
 
 
 
 
@contextmanager
def opening(filename):
    f = open(filename) # IOError is untouched by GeneratorContext
    try:
        yield f
    finally:
        f.close() # Ditto for errors here (however unlikely)
 
 
 

如上所述,就是with语句以及contextmanager decorator修饰器的语法解析。标准库里有些对象已经是context manager了,例如:

    - file
- thread.LockType
- threading.Lock
- threading.RLock
- threading.Condition
- threading.Semaphore
- threading.BoundedSemaphore

另外我们也可以通过contextlib.contextmanager来修饰我们的generator函数,作为context manager使用在with语句中。

With语句以及@contextmanager的语法解析的更多相关文章

  1. MySQL- -Join语法解析与性能分析

    Mysql Join语法解析与性能分析 一.Join语法概述 join 用于多表中字段之间的联系,语法如下: ... FROM table1 INNER|LEFT|RIGHT JOIN table2 ...

  2. 在.NET Core中使用Irony实现自己的查询语言语法解析器

    在之前<在ASP.NET Core中使用Apworks快速开发数据服务>一文的评论部分,.NET大神张善友为我提了个建议,可以使用Compile As a Service的Roslyn为语 ...

  3. Generator函数语法解析

    转载请注明出处: Generator函数语法解析 Generator函数是ES6提供的一种异步编程解决方案,语法与传统函数完全不同.以下会介绍一下Generator函数. 写下这篇文章的目的其实很简单 ...

  4. JSP编译成Servlet(一)语法树的生成——语法解析

    一般来说,语句按一定规则进行推导后会形成一个语法树,这种树状结构有利于对语句结构层次的描述.同样Jasper对JSP语法解析后也会生成一棵树,这棵树各个节点包含了不同的信息,但对于JSP来说解析后的语 ...

  5. 用java实现编译器-算术表达式及其语法解析器的实现

    大家在参考本节时,请先阅读以下博文,进行预热: http://blog.csdn.net/tyler_download/article/details/50708807 本节代码下载地址: http: ...

  6. 用java实现一个简易编译器-语法解析

    语法和解析树: 举个例子看看,语法解析的过程.句子:“我看到刘德华唱歌”.在计算机里,怎么用程序解析它呢.从语法上看,句子的组成是由主语,动词,和谓语从句组成,主语是“我”,动词是“看见”, 谓语从句 ...

  7. 使用 java 实现一个简单的 markdown 语法解析器

    1. 什么是 markdown Markdown 是一种轻量级的「标记语言」,它的优点很多,目前也被越来越多的写作爱好者,撰稿者广泛使用.看到这里请不要被「标记」.「语言」所迷惑,Markdown 的 ...

  8. Boost学习之语法解析器--Spirit

    Boost.Spirit能使我们轻松地编写出一个简单脚本的语法解析器,它巧妙利用了元编程并重载了大量的C++操作符使得我们能够在C++里直接使用类似EBNF的语法构造出一个完整的语法解析器(同时也把C ...

  9. Atitit.sql ast 表达式 语法树 语法 解析原理与实现 java php c#.net js python

    Atitit.sql ast 表达式 语法树 语法 解析原理与实现 java php c#.net js python 1.1. Sql语法树 ast 如下图锁死1 2. SQL语句解析的思路和过程3 ...

随机推荐

  1. FreeMark学习(二)

    (1)用户定义指令 宏和变换器变量是两种不同类型的用户定义指令,它们之间的区别是宏是在模板中使用macro指令定义,而变换器是在模板外由程序定义,这里只介绍宏 基本用法 宏是和某个变量关联的模板片断, ...

  2. Linux终端下安装jdk

    linux 终端下安装jdk(rpm方法) 1.下载jdk对应版本rpm文件(以下称为jdk.rpm) 放在对应文件夹下 2.使用命令给定权限 #chmod +x jdk.rpm 3.解压rpm文件 ...

  3. Kafka报错-as it has seen zxid 0x83808 our last zxid is 0x0 client must try another server

    as it has seen zxid 0x83808 our last zxid is 0x0 client must try another server 停止zookeeper,删除datadi ...

  4. Elasticsearch常用配置及性能参数

    cluster.name: estest   集群名称node.name: “testanya”  节点名称 node.master: false  是否主节点node.data: true   是否 ...

  5. js对象(一)

    1.创建对象 /*直接创建*/ var person = new Object(); person.name = "宝宝"; person.age = 3; var person2 ...

  6. python+selenium简易自动化框架,包含生成测试报告以及发送结果至Email

    Selenium+python环境搭建见虫师的pdf文档,非常详尽 简易框架: 1.文件目录:

  7. Android Studio中清单文件改versionCode和versionName没效果的原因

    在Android Studio中,项目的versionCode 和versionName 的控制不是在AndroidManifest.xml清单文件中更改的,而是在项目的build.gradle中更改 ...

  8. JavaScript 中this与Dom中的注意

    对于下面这段代码: <script type='text/javascript'> function testThis() { console.log(this); } </scri ...

  9. HTML Meta中添加X-UA-Compatible和IE=Edge,chrome=1有什么作用

    你好,这个属性主要是设置浏览器优先使用什么模式来渲染页面的.常见写法如下:<meta http-equiv="X-UA-Compatible" content="I ...

  10. 通过属性 Cesium的FBO主要支持两种方式

    角色其实就是一类权限的分组,所以给用户分配角色其实也是在给用户分配权限.在oracle中有三个比较常用的角色.对于一般不是很严格的系统可以授予开发用户CONNECT.RESOURCE角色权限即可. 其 ...