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. ARCGIS如何进行可视域分析

    可视域分析在不同的领域有着广泛的应用,如火灾监控点的设定,观察哨所的设定等等.军事领域是可视域分析技术应用最广的领域.例如为了设计巡航导弹的航线,就必须对发射点到目标的地形进行分析,包括地形特征优劣分 ...

  2. 那些年,我们一起追的面试题。。to be continued!!!

    1.什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?答:Java虚拟机是一个可以执行Java字节码的虚拟机进程.Java源文件被编译成能被Java虚拟机执行的字节码文件.Java被 ...

  3. DNS服务器原理

    19.1 什么是DNS   主机名自动解析为 IP 就很重要!那就是 DNS.   19.1.1 用网络主机名取得IP的历史渊源   单一档案处理上网的年代: /etc/hosts   利用某些特定的 ...

  4. xml对象的序列化和反序列化

    对象序列化: /// <summary>        /// 将一个对象序列化为XML字符串        /// </summary>        /// <par ...

  5. 机器学习PR:感知机模型

    感知机是二类分类的线性分类模型,所谓二分类指的是输出的类别只有-1或1两种,所谓线性指的是输入的特征向量集合在特征空间中被超平面划分为相互分离的正负两类.感知机学习的目的正是为了求出将训练数据进行线性 ...

  6. PostgreSQL系列一:PostgreSQL简介与安装

    一.PostgreSQL简介     1.1 PostgreSQL概述             PostgreSQL数据库是目前功能最强大的开源数据库,支持丰富的数据类型(如JSON和JSONB类型. ...

  7. golang中string以及slice之间的一些问题

    好记性不如烂笔头o_O slice切片不会开辟新的空间 a := []int{0,1,2,3} b := make([]int, 8) b = a[:] b[2] = 9 fmt.Println(a) ...

  8. HTML DOM Event对象

    我们通常把HTML DOM Event对象叫做Event事件 事件驱动模型 事件源:(触发事件的元素)事件源对象是指event对象 其封装了与事件相关的详细信息. 当事件发生时,只能在事件函数内部访问 ...

  9. Gvim插件管理Vundle

    之前关于gvim的博客中提到的很多插件安装比较繁琐,这一篇来介绍一下gvim管理插件的插件--vundle的安装以及使用,让givm的插件安装更加的简单 首先给出vundle的git地址 https: ...

  10. 读Javascript高级程序设计第三版第六章面向对象设计--创建对象

    虽然Object构造函数或者对象字面量都可以用来创建单个对象,但是缺点非常明显:使用同一接口创建很多对象,会产生大量重复代码. 工厂模式  1 function CreatePerson(name,a ...