Python With 用法

背景

  有一些任务,可能事先需要设置,事后做清理工作。对于这种场景,Python的with语句提供了一种非常方便的处理方式。

with如何工作?

  • 紧跟with后面的语句被求值后,返回对象的 __enter__() 方法被调用,这个方法的返回值将被赋值给as后面的变量。
  • 当with后面的代码块全部被执行完之后,将调用前面返回对象的 __exit__()方法。

例子

  1. #!/usr/bin/env python
  2. class Test(obj):
  3.     def __enter__(self):
  4.         print "In __enter__()"
  5.               return "test_with"
  6.     def __exit__(self, type, value, trace):
  7.         print "In __exit__()"
  8. def get_example():
  9.     return Test()
  10. with get_example() as example:
  11.     print "example:", example

     大家可以做下实验看下输出.

      __exit__ 方法有三个参数 val, type 和 trace。 这些参数在异常处理中相当有用。

     实际上,在with后面的代码块抛出任何异常时,__exit__() 方法被执行。正如例子所示,异常抛出时,与之关联的type,value和stack trace传给 __exit__() 方法,因此抛出的XX异常被打印出来了。开发库时,清理资源,关闭文件等等操作,都可以放在 __exit__ 方法当中。

     另外,__exit__ 除了用于tear things down,还可以进行异常的监控和处理,注意后几个参数。要跳过一个异常,只需要返回该函数True即可。

     下面的样例代码跳过了所有的TypeError,而让其他异常正常抛出。

  1. def __exit__(self, type, value, traceback):
  2.      return isinstance(value, TypeError)

      __exit__ 函数可以进行部分异常的处理,如果我们不在这个函数中处理异常,他会正常抛出,这时候我们可以这样写

  1. try:
  2.     with open( "a.txt" ) as f :
  3.         do something
  4. except xxxError:
  5.     do something about exception

 

contextlib 模块

contextlib 模块提供了3个对象:装饰器 contextmanager、函数 nested 和上下文管理器 closing。使用这些对象,可以对已有的生成器函数或者对象进行包装,加入对上下文管理协议的支持,避免了专门编写上下文管理器来支持 with 语句。

装饰器 contextmanager

contextmanager 用于对生成器函数进行装饰,生成器函数被装饰以后,返回的是一个上下文管理器,其 __enter__() 和 __exit__() 方法由 contextmanager 负责提供,而不再是之前的迭代子。被装饰的生成器函数只能产生一个值,否则会导致异常 RuntimeError;产生的值会赋值给 as 子句中的 target,如果使用了 as 子句的话。下面看一个简单的例子。

清单 9. 装饰器 contextmanager 使用示例
1
2
3
4
5
6
7
8
9
10
11
12
from contextlib import contextmanager
 
@contextmanager
def demo():
     print '[Allocate resources]'
     print 'Code before yield-statement executes in __enter__'
     yield '*** contextmanager demo ***'
     print 'Code after yield-statement executes in __exit__'
     print '[Free resources]'
 
with demo() as value:
     print 'Assigned Value: %s' % value

结果输出如下:

清单 10. contextmanager 使用示例执行结果
1
2
3
4
5
[Allocate resources]
Code before yield-statement executes in __enter__
Assigned Value: *** contextmanager demo ***
Code after yield-statement executes in __exit__
[Free resources]

可以看到,生成器函数中 yield 之前的语句在 __enter__() 方法中执行,yield 之后的语句在 __exit__() 中执行,而 yield 产生的值赋给了 as 子句中的 value 变量。

需要注意的是,contextmanager 只是省略了 __enter__() / __exit__() 的编写,但并不负责实现资源的“获取”和“清理”工作;“获取”操作需要定义在 yield 语句之前,“清理”操作需要定义 yield 语句之后,这样 with 语句在执行 __enter__() / __exit__() 方法时会执行这些语句以获取/释放资源,即生成器函数中需要实现必要的逻辑控制,包括资源访问出现错误时抛出适当的异常。

函数 nested

nested 可以将多个上下文管理器组织在一起,避免使用嵌套 with 语句。

清单 11. nested 语法
1
2
with nested(A(), B(), C()) as (X, Y, Z):
      # with-body code here

类似于:

清单 12. nested 执行过程
1
2
3
4
with A() as X:
     with B() as Y:
         with C() as Z:
              # with-body code here

需要注意的是,发生异常后,如果某个上下文管理器的 __exit__() 方法对异常处理返回 False,则更外层的上下文管理器不会监测到异常。

上下文管理器 closing

closing 的实现如下:

清单 13. 上下文管理 closing 实现
1
2
3
4
5
6
7
8
class closing(object):
     # help doc here
     def __init__(self, thing):
         self.thing = thing
     def __enter__(self):
         return self.thing
     def __exit__(self, *exc_info):
         self.thing.close()

上下文管理器会将包装的对象赋值给 as 子句的 target 变量,同时保证打开的对象在 with-body 执行完后会关闭掉。closing 上下文管理器包装起来的对象必须提供 close() 方法的定义,否则执行时会报 AttributeError 错误。

清单 14. 自定义支持 closing 的对象
1
2
3
4
5
6
7
8
9
10
11
12
class ClosingDemo(object):
     def __init__(self):
         self.acquire()
     def acquire(self):
         print 'Acquire resources.'
     def free(self):
         print 'Clean up any resources acquired.'
     def close(self):
         self.free()
 
with closing(ClosingDemo()):
     print 'Using resources'

结果输出如下:

清单 15. 自定义 closing 对象的输出结果
1
2
3
Acquire resources.
Using resources
Clean up any resources acquired.

closing 适用于提供了 close() 实现的对象,比如网络连接、数据库连接等,也可以在自定义类时通过接口 close() 来执行所需要的资源“清理”工作。

你可能感兴趣的:(Python)