Python with

简介

在编程中会经常碰到这种情况:有一个特殊的语句块,在执行这个语句块之前需要先执行一些准备动作;当语句块执行完成后,需要继续执行一些收尾动作。例如,文件读写后需要关闭,数据库读写完毕需要关闭连接,资源的加锁和解锁等情况。

对于这种情况python提供了上下文管理器(Context Manager)的概念,可以通过上下文管理器来定义/控制代码块执行前的准备动作,以及执行后的收尾动作。

一、为何使用上下文管理器

  1. 上下文管理器提供了 __enter__()方法和__exit__()方法,在with语句中,如果用as指定了一个目标,会将__enter__()方法的返回值赋予这个目标。
  2. 运行中如果发生了异常,那么将会把异常的类型,值和追踪传递给__exit__()方法。如果__exit__()方法返回值为true,那么这个异常将会被抑制,否则这个异常将会被重新抛出。
  3. 如果没有发生异常,也会调用__exit__()方法,但是传入的参数为None, None, None。通常也是在这里放入代码进行如文件流/会话的关闭等操作。

1、不使用上下文管理器的情况
通过try...finally语句执行异常处理和关闭句柄的动作。

 1 #coding:utf8
 2 
 3 logger = open("log.txt", "a+")
 4 try:
 5     logger.write("hello world!\n")
 6     
 7 finally:
 8     logger.close()
 9     f = open("log.txt","a+")
10     for i in f.readlines():
11         print i
12     f.close()
13 
14 print logger.closed
View Code
hello world!

True
运行结果

2、使用上下文管理器
默认文件Python的内置file类型是支持上下文管理协议的。
使用上下文管理器with使得依据精简了很多。

 1 #coding:UTF8
 2 with open("log.txt", "w") as logger:
 3     logger.write('Hello world!\n')
 4 
 5 
 6 print logger.closed
 7 f = open(r'log.txt')
 8 for line in f.readlines():
 9     print line
10 
11 f.close()
View Code
True
Hello world!
运行结果

 

二、实现上下文管理器实现上下文管理器有两种方式实现。方法一:类实现__enter__和__exit__方法。方法二:contextlib模块装饰器和生成器实现。
下面我们通过两种方法分别实现一个自定义的上下文管理器。
1、方法一:通过类实现__enter__和__exit__方法

 1 #coding:utf8
 2 
 3 class File(object):
 4     def __init__(self, file_name, method):
 5         self.file_obj = open(file_name, method)
 6     def __enter__(self):
 7         return self.file_obj
 8     def __exit__(self, type, value, traceback):
 9         self.file_obj.close()
10 
11 with File('demo.txt', 'w') as opened_file:
12     opened_file.write('Hola!')
View Code

实现__enter__和__exit__方法后,就能通过with语句进行上下文管理。

a、底层都发生了什么?

  1、with语句先暂存了File类的__exit__方法,然后它调用File类的__enter__方法。

  2、__enter__方法打开文件并返回给with语句,打开的文件句柄被传递给opened_file参数。

  3、with语句调用之前暂存的__exit__方法,__exit__方法关闭了文件。

 

b、异常处理

  关于异常处理,with语句会采取哪些步骤。

  1. 它把异常的type,value和traceback传递给__exit__方法

  2. 它让__exit__方法来处理异常

  3. 如果__exit__返回的是True,那么这个异常就被忽略。

  4. 如果__exit__返回的是True以外的任何东西,那么这个异常将被with语句抛出。

 1 #异常抛出,_exit__返回的是True以外的任何东西,那么这个异常将被with语句抛出
 2 class File(object):
 3     def __init__(self, file_name, method):
 4         self.file_obj = open(file_name, method)
 5     def __enter__(self):
 6         return self.file_obj
 7     def __exit__(self, type, value, traceback):
 8         self.file_obj.close()
 9         print "type:",type
10         print "value:",value
11         print "traceback:",traceback
12 
13 
14 with File('demo.txt', 'w') as opened_file:
15     opened_file.undefined_function('Hola!')
16 
17 #output================================================
18 
19 # type: <type 'exceptions.AttributeError'>
20 # value: 'file' object has no attribute 'undefined_function'
21 # traceback: <traceback object at 0x000000000262D9C8>
22 
23 #     opened_file.undefined_function('Hola!')
24 # AttributeError: 'file' object has no attribute 'undefined_function'
异常抛出
 1 #异常忽略,__exit__返回的是True,那么这个异常就被忽略。
 2 class File(object):
 3     def __init__(self, file_name, method):
 4         self.file_obj = open(file_name, method)
 5     def __enter__(self):
 6         return self.file_obj
 7     def __exit__(self, exception_type, exception_value, traceback):
 8         print("Exception has been handled")
 9         self.file_obj.close()
10         return True
11 
12 
13 with File('demo.txt', 'w') as opened_file:
14     opened_file.undefined_function('Hola!')
15     
16 # output==================================
17 # Exception has been handled
异常忽略:

 

2、方法二:contextlib模块装饰器和生成器实现

这种方式实现更优雅,我个人更喜欢这种方式。

yield之前的代码由__enter__方法执行,yield之后的代码由__exit__方法执行。本质上还是__enter__和__exit__方法。

 1 # coding:utf-8
 2 import contextlib
 3 
 4 
 5 @contextlib.contextmanager
 6 def myopen(filename, mode):
 7     f = open(filename, mode)
 8     try:
 9         yield f.readlines()
10     except Exception as e:
11         print e
12 
13     finally:
14         f.close()
15 
16 if __name__ == '__main__':
17     with myopen(r'c:\ip2.txt', 'r') as f:
18         for line in f:
19             print line
View Code

 

 3、with语句上多个下文关联

直接通过一个with语句打开多个上下文,即可同时使用多个上下文变量,而不必需嵌套使用with语句。

 1 class File(object):
 2     def __init__(self, file_name, method):
 3         self.file_obj = open(file_name, method)
 4     def __enter__(self):
 5         return self.file_obj
 6     def __exit__(self, exception_type, exception_value, traceback):
 7         self.file_obj.close()
 8         return True
 9 
10 
11 with File('demo.txt', 'w') as f1,File('demo.txt','w') as f2:
12     print f1,f2
13     
14 # Output============================# <open file 'demo.txt', mode 'w' at 0x000000000263D150> <open file 'demo.txt', mode 'w' at 0x000000000263D1E0>
View Code

 

posted @ 2017-02-23 18:26  浅雨凉  阅读(404)  评论(0编辑  收藏  举报