Python contextlib——上下文管理器

时间:2022-12-26 00:14:44

上下文管理器要负责一个代码块中的资源,可能在进入代码块时创建资源,然后在退出代码块时清理这个资源。

文件支持上下文管理器API,可以很容易地确保完成文件读写后关闭文件

?
1
2
3
>>> with open ( '/tmp/file.txt' , 'wt' ) as f:
...     f.write( 'continue to  goa here' )
...

上下文管理器由with语句启用,这个API包括两个方法。当执行流进入with中的代码块时会运行__enter__()方法。它会返回一个对象,在这个上下文中使用。当执行流离开with块时,则调用这个上下文管理器的__exit__()方法来清理所使用的资源。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> class Context( object ):
...     def __init__( self ):
...         print '__init__()'
...     def __enter__( self ):
...         print '__enter__'
...         return self
...     def __exit__( self ,exc_type, exc_val, exc_tb):
...         print '__exit__'
...
>>> with Context():
...     print 'Doing work in the context'
...
__init__()
__enter__
Doing work in the context
__exit__
>>>
使用as创建with语句中__enter__()返回的对象别名
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
>>> class WithinContext( object ):
...     def __init__( self , context):
...         print 'WithinContext.__init__(%s)' % context
...     def do_something( self ):
...         print 'WithinContext.do_something()'
...     def __del__( self ):
...         print 'WithinContext.__del__'
...
>>> class Context( object ):
...     def __init__( self ):
...         print 'Context.__init__()'
...     def __enter__( self ):
...         print 'Context.__enter__()'
...         return WithinContext( self )
...     def __exit__( self , exc_type, exc_val, exc_tb):
...         print 'Context.__exit__()'
...
>>> with Context() as c:
...     c.do_something()
...
Context.__init__()
Context.__enter__()
WithinContext.__init__(<__main__.Context object at 0xb74a2f6c >)
WithinContext.do_something()
Context.__exit__()
>>>

与变量c关联的值是__enter__()返回的对象,这不一定是with语句中创建的Context实例。

__exit__()方法接受一些参数,其中包含with块中产生的异常的详细信息。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
>>> class Context( object ):
...     def __init__( self , handle_error):
...         print '__init__(%s)' % handle_error
...         self .handle_error = handle_error
...     def __enter__( self ):
...         print '__enter__()'
...         return self
...     def __exit__( self , exc_type, exc_val, exc_tb):
...         print '__exit__()'
...         print '   exc_type = ' , exc_type
...         print '   exc_val = ' , exc_val
...         print '   exc_tb = ' , exc_tb
...         return self .handle_error
...
>>> with Context( True ):
...     raise RuntimeError( 'error message handled' )
...
__init__( True )
__enter__()
__exit__()
    exc_type =  < type 'exceptions.RuntimeError' >
    exc_val =  error message handled
    exc_tb =  <traceback object at 0xb74a334c >
>>>
>>> with Context( False ):
...     raise RuntimeError( 'error message propagated' )
...
__init__( False )
__enter__()
__exit__()
  exc_type =  < type 'exceptions.RuntimeError' >
  exc_val =  error message propagated
exc_tb =  <traceback object at 0xb74ce20c >
Traceback (most recent call last):
   File "<stdin>" , line 2 , in <module>
RuntimeError: error message propagated
>>>
如果上下文可以处理这个异常,__exit__() 应当返回一个true值来指示不需要传播这个异常,如果返回false,就会导致__exit__()返回后重新抛出这个异常。

从生成器到上下文管理器
使用contextmanager()修饰符将一个生成器函数转换成上下文管理器。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
   1 #! /usr/bin/python
   2 # -*- coding:utf-8 -*-
   3
   4 import contextlib
   5
   6 @contextlib.contextmanager
   7 def make_context():
   8     print '   entering'
   9     try :
  10         yield {}
  11     except RuntimeError, err:
  12         print '  ERROR:' , err
  13     finally :
  14         print '    exiting'
  15 print 'Normal:'
  16 with make_context() as value:
  17     print '    inside with statement:' , value
  18
  19 print '\nHandled error'
  20 with make_context() as value:
  21     raise RuntimeError( 'showing example of handling an error' )
  22
  23 print '\nUnhandled error:'
  24 with make_context() as value:
  25     raise ValueError( 'this exception is not handled' )
输出结果:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Normal:
    entering
     inside with statement: {}
     exiting
 
Handled error
    entering
   ERROR: showing example of handling an error
     exiting
 
Unhandled error:
    entering
     exiting
Traceback (most recent call last):
   File "contextlib_contextmanager.py" , line 25, in <module>
     raise ValueError( 'this exception is not handled' )
ValueError: this exception is not handled

生成器要初始化上下文,用yield生成一次值,然后清理上下文。所生成的值(如果有)会绑定到with语句as子句中的变量。with块中的异常会在生成器中重新抛出,使之在生成器中得到处理。

嵌套上下文

?
1
2
3
4
5
6
7
8
9
10
11
12
1 #! /usr/bin/python
  2 # -*- coding:utf-8 -*-
  3
  4 import contextlib
  5 @contextlib.contextmanager
  6 def make_context(name):
  7     print ' entering:' , name
  8     yield name
  9     print 'exiting:' , name
10
11 with make_context( 'A' ) as A, make_context( 'B' ) as B:
12     print 'inside with statement:' , A,B
输出结果
?
1
2
3
4
5
entering: A
  entering: B
inside with statement: A B
exiting: B
exiting: A
程序执行时会按其进入上下文的逆序离开上下文。每个上下文管理器与as子句(可选)之间用一个逗号(,)分隔。

关闭打开的句柄
file类直接支持上下文管理器API,不过表示打开句柄的另外一些对象并不支持这个API。contextlib的标准库文档给出的例子是一个由urllib.urlopen()返回的对象。还有一些遗留类,他们使用close()方法而不支持上下文管理器API。为了确保关闭句柄,需要使用closing()为它创建一个上下文管理器。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1 #! /usr/bin/python
  2 # -*- coding:utf-8 -*-
  3
  4 import contextlib
  5 class Door( object ):
  6     def __init__( self ):
  7         print '  __init__()'
  8     def close( self ):
  9         print '  close()'
10
11
12 print 'Normal Example:'
13 with contextlib.closing(Door()) as door:
14     print '  inside with statement'
15
16 print '\nError handling example:'
17 try :
18     with contextlib.closing(Door()) as door:
19         print '  raiseing from inside with statement'
20         raise RuntimeError( 'error message' )
21 except Exception, err:
22     print '  Had an error:' , err
输出结果:
?
1
2
3
4
5
6
7
8
9
10
Normal Example:
   __init__()
   inside with statement
   close()
 
Error handling example:
   __init__()
   raiseing from inside with statement
   close()
   Had an error: error message
不论with块中是否有一个错误,这个句柄都会关闭。