with 语句那点事

来源:互联网 发布:淘宝足球竞猜 编辑:程序博客网 时间:2024/05/20 21:58

在操作打开读取文件时,经常会看到如下代码:


with open('python.txt') as f:
   for line in f:
       print line


代码非常简洁,但是你是否深入理解这其中具体流程呢? 


这段代码的作用是:根据文件名打开一个文件,如果无异常抛出,把文件对象赋值给f,然后用迭代器遍历文件中每一行,当完成时,关闭文件;而无论在这段代码的任何地方,如果发生异常,此时文件仍会被关闭。


你是否好奇,没有异常处理,没有 try ... except ... finally ... 的代码块,异常是如何处理的呢?


这是通过上下文对象来完成处理的。with 语句的通用结构如下:


with context_expr as var:
   with_suite


其中,context_expr 为一上下文表达式,执行此表达式会返回一个上下文管理器。上下文管理器的职责是提供一个上下文对象,这是通过调用 __context__() 方法来完成的,这个方法返回上下文对象。上下文管理器本身也可以是上下文对象,即调用 __context__() 方法返回其本身。


上下文对象有什么特点呢?顾名思义,上下文,上文和下文,分别需要实现两个方法:


  • __enter__() 方法将完成 with 语句块执行前的所有准备工作,如果 with 语句后面跟了 as var 语句,则 var 即为 __enter__() 方法的返回值。

  • __exit__() 方法将完成 with 语句块执行后的所有处理工作,__exit__() 方法有3个参数,如果 with 语句正常结束,三个参数全部都是 None;如果发生异常,三个参数的值分别等于调用 sys.exc_info() 函数返回的三个值:类型(异常类)、值(异常实例)和跟踪记录(traceback),相应的跟踪记录对象。


由此可见,并非所有的对象都是上下文对象,那么常用的有哪些?小编常用的主要有:


  • file

  • threading.Lock

  • threading.RLock

  • threading.Condition

  • threading.Semaphore


这几种,这几种有什么特点?你会发现这几种都是资源。没错,with 语句很常用的功能,就是在执行语句块前申请获取相关的资源,而在 with 语句块执行完成之后,需要释放相关资源


当然,明确了上下文对象的原理,我们也可以自己定义上下文对象:


class A:
   def __enter__(self):
       print '__enter__() called'

   
def __exit__(self, type, value, traceback):
       print '__exit__() called'
       
if type is ZeroDivisionError:
           print('Please DO NOT Divide By Zero!')
           return True

with A() as a:
   print('In With Code Block!')
   b = 1/0


运行的结果如下:


__enter__() called

In With Code Block!

__exit__() called

Please DO NOT Divide By Zero!


可以发现,异常被正确处理了。 注意,遇见异常处理后需要返回 True,告诉解释器异常被正确处理了,否则会抛出异常。


在 Python 中,contextlib 模块允许我们使用其中的 contextmanager 函数作为装饰器,来创建一个上下文管理器。这样,就不需要定义上下文对象的两个方法 __enter__() 和  __exit__() (实际是 contextmanager 函数来完成转换),只需实现有一个 yield 语句的生成器,使得 yield 返回值为 __enter__ () 方法返回的值即可。


在使用 @contextmanager 装饰的生成器中,yield 语句将函数的定义体分成两部分:第一部分是yield 语句前面的所有代码,在 with 代码块开始前(即解释器调用 __enter__ () 方法时)执行,yield 的返回值即为 __enter__ () 方法的返回值; 第二部分是 yield 语句后面的代码,在 with 代码块结束时(即调用 __exit__() 方法时)执行。


由此,我们可以把自己实现的上下文对象改写为如下形式:


from contextlib import contextmanager
@contextmanager
def A():
   try:
       print '__enter__() called'
       
yield
   except
ZeroDivisionError:
       print('Please DO NOT Divide By Zero!')
   finally:
       print '__exit__() called'

with A():
   print('In With Code Block!')
   b = 1/0


运行结果为:


__enter__() called

In With Code Block!

Please DO NOT Divide By Zero!

__exit__() called


异常也被正确处理了。


你是否学会了 with 语句的使用,是否深入理解了上下文对象?




原创粉丝点击