Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5119906
  • 博文数量: 921
  • 博客积分: 16037
  • 博客等级: 上将
  • 技术积分: 8469
  • 用 户 组: 普通用户
  • 注册时间: 2006-04-05 02:08
文章分类

全部博文(921)

文章存档

2020年(1)

2019年(3)

2018年(3)

2017年(6)

2016年(47)

2015年(72)

2014年(25)

2013年(72)

2012年(125)

2011年(182)

2010年(42)

2009年(14)

2008年(85)

2007年(89)

2006年(155)

分类: Python/Ruby

2015-06-23 14:05:30

这篇文章主要介绍了Python 的 with 语句,本文详细讲解了with语句、with语句的历史、with语句的使用例子等,需要的朋友可以参考下

一、简介

with是从Python 2.5 引入的一个新的语法,更准确的说,是一种上下文的管理协议,用于简化try…except…finally的处理流程。with通过__enter__方法初始化,然后在__exit__中做善后以及处理异常。对于一些需要预先设置,事后要清理的一些任务,with提供了一种非常方便的表达。

with的基本语法如下,EXPR是一个任意表达式,VAR是一个单一的变量(可以是tuple),”as VAR”是可选的。


  1. with EXPR as VAR:
  2.     BLOCK
根据PEP 343的解释,with…as…会被翻译成以下语句:


  1. mgr = (EXPR)
  2. exit = type(mgr).__exit__ # Not calling it yet
  3. value = type(mgr).__enter__(mgr)
  4. exc = True
  5. try:
  6.     try:
  7.         VAR = value # Only if "as VAR" is present
  8.         BLOCK
  9.     except:
  10.         # The exceptional case is handled here
  11.         exc = False
  12.         if not exit(mgr, *sys.exc_info()):
  13.             raise
  14.         # The exception is swallowed if exit() returns true
  15. finally:
  16.     # The normal and non-local-goto cases are handled here
  17.     if exc:
  18.         exit(mgr, None, None, None)
为什么这么复杂呢?注意finally中的代码,需要BLOCK被执行后才会执行finally的清理工作,因为当EXPR执行时抛出异常,访问mgr.exit执行就会报AttributeError的错误。



二、实现方式

根据前面对with的翻译可以看到,被with求值的对象必须有一个__enter__方法和一个__exit__方法。稍微看一个文件读取的例子吧,注意在这里我们要解决2个问题:文件读取异常,读取完毕后关闭文件句柄。用try…except一般会这样写:


  1. f = open('/tmp/tmp.txt')
  2. try:
  3.     for line in f.readlines():
  4.         print(line)
  5. finally:
  6.     f.close()
注意我们这里没有处理文件打开失败的IOError,上面的写法可以正常工作,但是对于每个打开的文件,我们都要手动关闭文件句柄。如果要使用with来实现上述功能,需要需要一个代理类:


  1. class opened(object):
  2.     def __init__(self, name):
  3.         self.handle = open(name)
  4.     def __enter__(self):
  5.         return self.handle
  6.     def __exit__(self, type, value, trackback):
  7.         self.handle.close()
  8. with opened('/tmp/a.txt') as f:
  9.     for line in f.readlines():
  10.         print(line)

注意我们定了一个名字叫opened的辅助类,并实现了__enter__和__exit__方法,__enter__方法没有参数,__exit__方法的3个参数,分别代表异常的类型、值、以及堆栈信息,如果没有异常,3个入参的值都为None。


如果你不喜欢定义class,还可以用Python标准库提供的contextlib来实现:



  1. from contextlib import contextmanager
  2. @contextmanager
  3. def opened(name):
  4.     f = open(name)
  5.     try:
  6.         yield f
  7.     finally:
  8.         f.close()
  9. with opened('/tmp/a.txt') as f:
  10.     for line in f.readlines():
  11.         print(line)

使用contextmanager的函数,yield只能返回一个参数,而yield后面是处理清理工作的代码。在我们读取文件的例子中,就是关闭文件句柄。这里原理上和我们之前实现的类opened是相同的,有兴趣的可以参考一下contextmanager的源代码。


三、应用场景

废话了这么多,那么到底那些场景下该使用with,有没有一些优秀的例子?当然啦,不然这篇文章意义何在。以下摘自PEP 343。

一个确保代码执行前加锁,执行后释放锁的模板:

  1. @contextmanager
  2.     def locked(lock):
  3.         lock.acquire()
  4.         try:
  5.             yield
  6.         finally:
  7.             lock.release()
  8.     with locked(myLock):
  9.         # Code here executes with myLock held. The lock is
  10.         # guaranteed to be released when the block is left (even
  11.         # if via return or by an uncaught exception).


数据库事务的提交和回滚:


  1. @contextmanager
  2.         def transaction(db):
  3.             db.begin()
  4.             try:
  5.                 yield None
  6.             except:
  7.                 db.rollback()
  8.                 raise
  9.             else:
  10.                 db.commit()
重定向stdout:


  1. @contextmanager
  2. def stdout_redirected(new_stdout):
  3.     save_stdout = sys.stdout
  4.     sys.stdout = new_stdout
  5.     try:
  6.         yield None
  7.     finally:
  8.         sys.stdout = save_stdout
  9. with opened(filename, "w") as f:
  10.     with stdout_redirected(f):
  11.         print "Hello world"

注意上面的例子不是线程安全的,再多线程环境中要小心使用。



四、总结

with是对try…expect…finally语法的一种简化,并且提供了对于异常非常好的处理方式。在Python有2种方式来实现with语法:class-based和decorator-based,2种方式在原理上是等价的,可以根据具体场景自己选择。

with最初起源于一种block…as…的语法,但是这种语法被很多人所唾弃,最后诞生了with,关于这段历史依然可以去参考PEP-343和PEP-340





阅读(930) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~