主题:从with关键字到编写自己简单的ContextManager

2012-05-24  来源:本站原创  分类:Python  人气:0 

contextlib.contextmanager的用法是怎样的?我摘抄一下模块源代码

引用

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>

大致就是可以把程序中的主体,<body>从中抽取出来,放到with块之中。而之后处理的<finally>都可以放到some_generator函数里面。
some_generator函数的编写很好办。只是contextmanager这个装饰器应该作为一个函数好呢还是一个类好呢?这是个很有意思的问题,我也想了好一会,等下一起讨论下。先给出我的解法:

Python代码

  1. class MyGeneratorContextManager(object):
  2. def __init__(self, gen):
  3. print("__init__ called")
  4. self.gen = gen
  5. def __enter__(self):
  6. print("__enter__ called")
  7. return self.gen.next()
  8. def __exit__(self, exc_type, exc_val, exc_tb):
  9. print("__exit__called exc_type = %s, exc_val = %s, exc_tb = %s"\
  10. % (exc_type, exc_val, exc_tb))
  11. # 这里没有做异常处理,需要处理StopIteration异常
  12. # 不是用return也可以
  13. # 下面这句话将输出yield [1, 2, 3]后面的的打印语句end foo
  14. return self.gen.next()
  15. def MyContextManager(func):
  16. def tmpf(*args):
  17. print("func info:", func)
  18. return MyGeneratorContextManager(func(*args))
  19. return tmpf
  20. @MyContextManager
  21. def foo(val):
  22. # 尝试用老方法捕捉错误
  23. try:
  24. print("start foo", val)
  25. yield [1, 2, 3]
  26. # 下面一行需要调用self.gen.next()才能输出
  27. print("end foo")
  28. except (Exception, AssertionError):
  29. # 但是实际上并没有捕捉到yield中的错误
  30. # except的功能完全被__exit__取代
  31. print("EXCEPTION ENCOUNTERED!")
  32. finally:
  33. print("FINALLY")
  34. print("foo is ", foo)
  35. print("foo() is ", foo("bbbb"))
  36. print("\nWITH INFO BELOW:")
  37. with foo("aaaa") as tmp:
  38. print("START WITH")
  39. #: tmp实际上就是yield穿过来的值
  40. print(tmp)
  41. for i in tmp:
  42. print(i)
  43. assert 1>2
  44. # 出错之后直接从with中跳出去,下面不可能被执行
  45. print("END WITH")

输出结果是:

Python代码

  1. # 首先可以看到foo的值是闭包中的tmpf函数
  2. ('foo is ', <function tmpf at 0x7fb78b15f140>)
  3. ('func info:', <function foo at 0x7fb78b15f0c8>)
  4. __init__ called
  5. ('foo() is ', <__main__.MyGeneratorContextManager object at 0x7fb78b1591d0>)
  6. WITH INFO BELOW:
  7. # 请看,两次调用foo(),发现他们最终都是同一个foo函数
  8. ('func info:', <function foo at 0x7fb78b15f0c8>)
  9. # 但是奇怪的是,函数被初始化了两次?这是因为这是个工厂模式,每次调用的函数虽然一样,但是会生成不同的类
  10. __init__ called
  11. __enter__ called
  12. ('start foo', 'aaaa')
  13. START WITH
  14. [1, 2, 3]
  15. 1
  16. 2
  17. 3
  18. # assert触发的错误没有被except捕捉到!被__exit__函数捕捉到了
  19. __exit__called exc_type = <type 'exceptions.AssertionError'>, exc_val = , exc_tb = <traceback object at 0x7fb78b15c2d8>
  20. end foo
  21. FINALLY
  22. # 为什么跳出StopIteration异常?这是因为gen.next()已经走到头了,我们没有处理这异常
  23. Traceback (most recent call last):
  24. File "/home/leonardo/Aptana Studio 3 Workspace/PythonStudy/src/thinking/mycontext_manager.py", line 68, in <module>
  25. print("END WITH")
  26. File "/home/leonardo/Aptana Studio 3 Workspace/PythonStudy/src/thinking/mycontext_manager.py", line 31, in __exit__
  27. return self.gen.next()
  28. StopIteration

首先创建了一个工厂模式的MyContextManager,它实际上又是个闭包函数,也可以作为装饰器方便以后使用。

其次我定义了一个类MyGeneratorContextManager,这个函数在初始化的时候,就接收一个generator。请注意,接收的 是generator而不是function
请看函数

Python代码

  1. def tmpf(*args):
  2. print("func info:", func)
  3. return MyGeneratorContextManager(func(*args))

func在这里虽然是一个函数,但是func(*args)是一个generator,忘记传参数就糟了

在__enter__中最紧要的是要

Python代码

  1. return self.gen.next()

因为我们在with中面对的是一个generator,如果不对其进行next(),这个函数是不会动的。

进入到with块之后,foo函数里yield的值会直接传给tmp,这个值无关紧要。with块中所有语句就好像全部被填到yield那个地方去了一样。

程序于是执行with块中的语句,当其中出现异常的时候,我们外围的except语句块应该迅速捕捉到这一点,并输出才对。实际上不是,实际上当foo函数出现异常的时候,__exit__函数是第一时间捕捉到这个异常的。通过它的打印信息我们可以看出。

当处理完这个异常之后,我们调用了一下self.gen.next(),这个语句保证field语句后面的语句会被执行。最后执行了finally中的内容。你看except语句块被完全架空了。

好,我们回过头来再看,我们到底实现了什么东西?我们想执行的是

引用

<setup>
try:
<variable> = <value>
<body>
finally:
<cleanup>

现在我们把body单独抽出来了,放到with当中,把variable value替换成了yield value。这样做到掐头去尾,把前后不变的东西拿了出来,把内部的东西抽了出来。
咦?这不就是个装饰器么?错。装饰器是保持真身不懂,把前后改了,这是两个不同的方面。

最后我们回到我一开始提出的问题,为什么要设计一个mycontextmanager函数?可不可以不用工厂模式,直接用MyGeneratorContextManager函数一步到位?
这有两个前提条件要注意。第一,如果用class作为装饰器,这个类必须是可调用的(callable),如果将来我们要使用它,只可能它的调用__call__函数,因为它又要实现能在call之后调用__exit__,所以它只能返回self, type(self)之类的东西。第二,因为foo()是用在with语句中的,所以它必须是一个generator,也就是说foo.__call__()的返回结果是一个generator。那么可以找到有这么一个返回值,它既是self, type(self)这样类相关的类型,而且还是一个包含yield的generator么?:-)

要是对yield还不熟悉的朋友可能现在还不是很清楚,尤其是generator函数和with块中代码的关系,很可能还有一些把这代码再折腾折腾的想法。嗯,这里我就不写了,有问题给我留言吧!

相关文章
  • 主题:从with关键字到编写自己简单的ContextManager 2012-05-24

    contextlib.contextmanager的用法是怎样的?我摘抄一下模块源代码 引用 Typical usage: @contextmanager def some_generator(<arguments>): <setup> try: yield <value> finally: <cleanup> This makes this: with some_generator(<arguments>) as <variable>

  • 用Python编写一个简单的Lisp解释器的教程 2015-02-22

    这篇文章主要介绍了用Python编写一个简单的Lisp解释器的教程,Lisp是一种源码简单的函数式编程语言,本文主要介绍对其中的一个子集Scheme的解释器开发,需要的朋友可以参考下 本文有两个目的: 一是讲述实现计算机语言解释器的通用方法,另外一点,着重展示如何使用Python来实现Lisp方言Scheme的一个子集.我将我的解释器称之为Lispy (lis.py).几年前,我介绍过如何使用Java编写一个Scheme解释器,同时我还使用Common Lisp语言编写过一个版本.这一次,我的目

  • php编写的简单页面跳转功能实现代码 2013-12-17

    这篇文章主要介绍了php编写的简单页面跳转功能实现代码,有需要的朋友可以参考一下 不多说,直接上代码 //链接数据库'查询 mysql_connect('localhost','username','userpwd')or die("数据库链接失败".mysql_error()); mysql_select_db('库名'); mysql_query('set names utf8'); $sql1="select * from user "; $query1=my

  • 使用Python编写一个简单的tic-tac-toe游戏的教程 2014-01-15

    这篇文章主要介绍了使用Python编写一个简单的tic-tac-toe游戏的教程,有利于Python初学者进行上手实践,需要的朋友可以参考下 这个教程,我们将展示如何用python创建一个井字游戏. 其中我们将使用函数.数组.if条件语句.while循环语句和错误捕获等. 首先我们需要创建两个函数,第一个函数用来显示游戏板: def print_board(): for i in range(0,3): for j in range(0,3): print map[2-i][j], if j !

  • 用Python编写一个简单的俄罗斯方块游戏的教程 2014-02-12

    这篇文章主要介绍了用Python编写一个简单的俄罗斯方块游戏的教程,编写俄罗斯方块几乎是每门编程语言基础学习后的必备实践,需要的朋友可以参考下 俄罗斯方块游戏,使用Python实现,总共有350+行代码,实现了俄罗斯方块游戏的基本功能,同时会记录所花费时间,消去的总行数,所得的总分,还包括一个排行榜,可以查看最高记录. 排行榜中包含一系列的统计功能,如单位时间消去的行数,单位时间得分等. 附源码: from Tkinter import * from tkMessageBox import *

  • 用Python编写一个简单的FUSE文件系统的教程 2014-05-26

    这篇文章主要介绍了用Python编写一个简单的FUSE文件系统的教程,对于数据的备份很有帮助,需要的朋友可以参考下 如果你是我的长期读者,那么你应该知道我在寻找一个完美备份程序,最后我写了一个基于bup的我自己的加密层. 在写encbup的时候,我对仅仅恢复一个文件就必须要下载整个巨大的档案文件的做法不甚满意,但仍然希望能将EncFS和 rdiff-backup一起使用来实现可远程挂载.加密.去重.版本化备份的功能. 再次试用obnam 后(??乱痪洌核?故锹?某銎?,我注意到了它有一个moun

  • php编写一个简单的路由类 2014-06-10

    php编写一个简单的路由类,需要的朋友可以参考下. 类代码: <?php class Router { public function getRouter($types = 1) { if ( isset($_SERVER['PATH_INFO']) ) { $query_string = substr(str_replace(array('.html','.htm', '.asp', '//'), '',$_SERVER['PATH_INFO']),1); } else { $query_st

  • 编写一个简单的JavaScript模板引擎 2014-03-10

    随着Nodejs的流行,JavaScript在前端和后端都开始流行起来.有许多成熟的JavaScript模板引擎,例如Swig,既可以用在后端,又可以用在前端. 不过很多时候,前端模板仅仅需要简单地创建一个HTML片段,用Swig这种全功能模板有点大材小用.我们来尝试自己编写一个简单的前端模板引擎,实际上并不复杂. 在编写前端模板引擎代码之前,我们应该想好如何来调用它,即这个模板引擎的接口应该是什么样的.我们希望这样调用它: // 创建一个模板引擎: var tpl = new Template

  • 用 C 语言编写一个简单的垃圾回收器 2014-09-23

    人们似乎认为编写垃圾回收机制是很难的,是一种只有少数智者和Hans Boehm(et al)才能理解的高深魔法.我认为编写垃圾回收最难的地方就是内存分配,这和阅读K&R所写的malloc样例难度是相当的. 在开始之前有一些重要的事情需要说明一下:第一,我们所写的代码是基于Linux Kernel的,注意是Linux Kernel而不是GNU/Linux.第二,我们的代码是32bit的.第三,请不要直接使用这些代码.我并不保证这些代码完全正确,可能其中有一些我 还未发现的小的bug,但是整体思路仍

  • JQuery入门-编写一个简单的JQuery应用案例 2014-07-20

    首先引入JQuery文件库只需将文件导入页面中即可,即在<head></head>中,接下来详细介绍,感兴趣的朋友可以了解下 一.官方网站下载:http://jquery.com 二.引入JQuery文件库 下载完后不用安装,只需将文件导入页面中即可,即在<head></head>中加入如下代码:<script language="javascript" type="text/javascript" src=&q

  • 编写一个简单的可加载内核模块 2013-09-28

    前言:本文主要介绍了LKM的基本概念和如果配置编译环境,编译加载一个简单的LKM.适合linux初学者. 什么是可加载内核模块 可加载内核模块(Loadable Kernel Module,LKM允许在不重编译内核和重启系统的条件下对类Unix系统的系统内核进行修改和扩展.大多数的Unix派生系统,包括Linux,BSD,OSX等都支持这个特性. 本文着重描述编译执行一个"hello world"LKM的过程.相关的代码可以在这里下载.下面的命令和代码都在Ubuntu 11.10和12

  • 一个用php3编写的简单计数器 2014-03-28

    php具有极其强大的图像处理能力,用它可以很轻易的动态生成web图像. 一下是一个使用php做成的一个简单计数器. 1. 总体思路: 把以往的访问人数记录在一个文本文件中,当网页被访问的时候,从打开该文件 并从中读出以往的访问人数,加 1,得到最新的访问人数,并把该数目格式化成 标准的格式,再调用图像处理函数,把该数字输出成图片,再把新的访问数字回 写到纪录访问人数的文件中. 2. 程序所用到的函数说明: A. 相关的文件操作: a. 打开文件: 函数的原型:int fopen(string f

  • 为一个 iOS 应用编写一个简单的 Node.js/MongoDB Web 服务 2014-11-03

    在当今这个协作和社交应用的世界里,其关键是要有一个能简单构建和易于部署的后台.许多组织机构都依赖于一个应用栈(Application Stack),其使用下面三项技术: Node.js Express MongoDB 这个栈对于移动应用来说相当流行,因为原生数据格式是JSON,它容易被应用解析,例如通过使用 Cocoa 的NSJSONSerialization类或其它类似的解析器. 在本教程中,你将学会如何搭建了一个 Node.js 环境,驱动 Express:在此平台之上,你将构建一个通过 R

  • c#编写的简单登录(login)窗口 2015-04-12

    启动VC 2010 代码如下: this.AcceptButton = this.btnOK; this.CancelButton = this.btnCancel; private void btnOK_Click(object sender, System.EventArgs e) { // Here is to use fixed username and password // You can check username and password from DB if( txtUser

  • 如何编写简单的Shell脚本(Script)文件之Linux的基本操作 2014-02-26

    如何编写简单的Shell脚本(Script)文件之Linux的基本操作 新建一个文本文件包含所需要的脚本.举例,我会使用pico编辑器写一个脚本用来运行程序tar,带上必要的可选项可以用来解压从因特网下载下来的*.tar的文件(我好像总是记不住tar的所有参赛).滑动轴承 我决定把我的脚本名称叫做"untar": pico untar 因为在我的当前工作目录里untar文件不存在,所有pico文本编辑器自动创建这个文件,现在,我输入以下内容: #!/bin/bash echo this

  • 如何编写PHP扩展 2011-04-19

    如何编写PHP扩展 翻译:[email protected] Ver 0.1 最后修改日期 2006/1/19 WJL Studio @ wjl.cn 2006 目 录 简 介 快速开始 内存管理 从PHP函数中返回值 完成self_concat() 实例小结 包裹第三方的扩展 编写利用资源的第一个PHP函数 全局变量 添加自定义INI指令 线程安全资源管理宏 总 结 词汇表 简 介 PHP取得成功的一个主要原因之一是她拥有大量的可用扩展.web开发者无论有何种需求,这种需求最有可能在PHP发行包里找到.P

  • 编写PHP扩展 2014-02-26

    PHP取得成功的一个主要原因之一是她拥有大量的可用扩展.web开发者无论有何种需求,这种需求最有可能在PHP发行包里找到.PHP发行包包括支持各种数据库,图形文件格式,压缩,XML技术扩展在内的许多扩展. 扩展API的引入使PHP3取得了巨大的进展,扩展API机制使PHP开发社区很容易的开发出几十种扩展.现在,两个版本过去了,API仍然和PHP3时的非常相似.扩展主要的思想是:尽可能的从扩展编写者那里隐藏PHP的内部机制和脚本引擎本身,仅仅需要开发者熟悉API. 有两个理由需要自己编写PHP扩展

  • 30分钟搭建Python的Flask框架并在上面编写第一个应用 2014-01-05

    这篇文章主要介绍了如何搭建Python的Flask框架并在上面编写一个简单的登录模版应用,代码数量少.充分体现了Flask框架的轻量与开发高效的特点,需要的朋友可以参考下 Flask 是一种很赞的Python web框架.它极小,简单,最棒的是它很容易学. 今天我来带你搭建你的第一个Flask web应用!和官方教程 一样,你将搭建你自己的微博客系统:Flaskr.和官方Flask教程不同的是--我们通过使用Stormpath来创建并管理用户账户和数据,你的工作效率会更高.开发进程会显著地加快!

  • java简单实现复制 粘贴 剪切功能代码分享 2014-03-19

    本文给大家分享了一段java编写的简单实现复制粘贴剪切功能的代码,需要的小伙伴可以直接拿走使用.如有更好的方案,也可以告之本人. 废话不多说,直接上代码,小伙伴们仔细看下注释吧. /*简单的复制 剪切 粘贴 功能 操作: 复制测试: 输入文本选择文本,点击复制,然后将光标放在右边的TextArea,点击粘贴 剪切测试:输入文本选择文本,然后将光标放在右边的TextArea,点击剪切 */ import javax.swing.*; import java.awt.*; import java.a

  • 基于VC编写COM连接点事件的分析介绍 2014-08-28

    本篇文章是对VC编写COM连接点事件进行了详细的分析介绍,需要的朋友参考下 COM 中的典型方案是让客户端对象实例化服务器对象,然后调用这些对象.然而,没有一种特殊机制的话,这些服务器对象将很难转向并回调到客户端对象.COM 连接点便提供了这种特殊机制,实现了服务器和客户端之间的双向通信.使用连接点,服务器能够在服务器上发生某些事件时调用客户端. 原理如下图: 有了连接点,服务器可通过定义一个接口来指定它能够引发的事件.服务器上引发事件时,要采取操作的客户端会向服务器进行自行注册.随后,客户端会