|
|
|
|
移动端

2.2.4 上下文管理器——with语句

《Python高级编程(第2版)》第2章语法最佳实践——类级别以下,本章将介绍现在这门语言的语法中最重要的元素,以及它们的使用技巧。本节为大家介绍上下文管理器——with语句。

作者:张亮/阿信 译来源:人民邮电出版社|2018-01-29 18:34

技术沙龙 | 6月30日与多位专家探讨技术高速发展下如何应对运维新挑战!


2.2.4 上下文管理器——with语句

为了确保即使在出现错误的情况下也能运行某些清理代码,try...finally语句是很有用的。这一语句有许多使用场景,例如:

关闭一个文件。

释放一个锁。

创建一个临时的代码补丁。

在特殊环境中运行受保护的代码。

with语句为这些使用场景下的代码块包装提供了一种简单方法。即使该代码块引发了异常,你也可以在其执行前后调用一些代码。例如,处理文件通常采用这种方式:

  1. >>> hosts = open('/etc/hosts')  
  2. >>> try:  
  3. ...     for line in hosts:  
  4. ...         if line.startswith('#'):  
  5. ...             continue  
  6. ...         print(line.strip())  
  7. ... finally:  
  8. ...     hosts.close()  
  9. ...  
  10. 127.0.0.1       localhost  
  11. 255.255.255.255 broadcasthost  
  12. ::1             localhost  

本示例只针对Linux系统,因为要读取位于etc文件夹中的主机文件,但任何文本文件都可以用相同的方法来处理。

利用with语句,上述代码可以重写为:

  1. >>> with open('/etc/hosts') as hosts:  
  2. ...     for line in hosts:  
  3. ...         if line.startswith('#'):  
  4. ...             continue  
  5. ...         print(line.strip())  
  6. ...  
  7. 127.0.0.1       localhost  
  8. 255.255.255.255 broadcasthost  
  9. ::1             localhost 

在前面的示例中,open的作用是上下文管理器,确保即使出现异常也要在执行完for循环之后关闭文件。

与这条语句兼容的其他项目是来自threading模块的类:

threading.Lock

threading.RLock

threading.Condition

threading.Semaphore

threading.BoundedSemaphore

一般语法和可能的实现

with语句的一般语法的最简单形式如下:

  1. with context_manager:  
  2.     # 代码块  
  3.     ... 

此外,如果上下文管理器提供了上下文变量,可以用as子句保存为局部变量:

  1. with context_manager as context:  
  2.     # 代码块  
  3.     ... 

注意,多个上下文管理器可以同时使用,如下所示:

  1. with A() as a, B() as b:  
  2.     ... 

这种写法等价于嵌套使用,如下所示:

  1. with A() as a:  
  2.     with B() as b:  
  3.         ... 

(1)作为一个类

任何实现了上下文管理器协议(context manager protocol)的对象都可以用作上下文管理器。该协议包含两个特殊方法。

__ enter __ (self):更多内容请访问https://docs.python.org/3.3/reference/datamodel.html #object.enter。

__ exit __ (self, exc _ type, exc _ value, traceback):更多内容请访问https://docs.python.org/3.3/reference/datamodel.html#object.exit
简而言之,执行with语句的过程如下:

调用__ enter __方法。任何返回值都会绑定到指定的as子句。

执行内部代码块。

调用__ exit __方法。

__ exit __接受代码块中出现错误时填入的3个参数。如果没有出现错误,那么这3个参数都被设为None。出现错误时,__ exit __不应该重新引发这个错误,因为这是调用者(caller)的责任。但它可以通过返回True来避免引发异常。这可用于实现一些特殊的使用场景,例如下一节将会看到的contextmanager装饰器。但在大多数使用场景中,这一方法的正确行为是执行类似于finally子句的一些清理工作,无论代码块中发生了什么,它都不会返回任何内容。

下面是某个实现了这一协议的上下文管理器示例,以更好地说明其工作原理:

  1. class ContextIllustration:  
  2.        def __enter__(self):  
  3.            print('entering context')  
  4.        def __exit__(self, exc_type, exc_value, traceback):  
  5.            print('leaving context')  
  6.  
  7.            if exc_type is None:  
  8.                print('with no error')  
  9.            else:  
  10.                print('with an error (%s)' % exc_value) 

没有引发异常时的运行结果如下:

  1. >>> with ContextIllustration():  
  2. ...     print("inside")  
  3. ...  
  4. entering context  
  5. inside  
  6. leaving context  
  7. with no error 

引发异常时的输出如下:

  1. >>> with ContextIllustration():  
  2. ...     raise RuntimeError("raised within 'with'")  
  3. ...  
  4. entering context  
  5. leaving context  
  6. with an error (raised within 'with')  
  7. Traceback (most recent call last):  
  8.   File "< input >", line 2, in < module > 
  9. RuntimeError: raised within 'with' 

(2)作为一个函数——contextlib模块

使用类似乎是实现Python语言提供的任何协议最灵活的方法,但对许多使用场景来说可能样板太多。标准库中新增了contextlib模块,提供了与上下文管理器一起使用的辅助函数。它最有用的部分是contextmanager装饰器。你可以在一个函数里面同时提供__ enter __和__ exit __两部分,中间用yield语句分开(注意,这样函数就变成了生成器)。用这个装饰器编写前面的例子,其代码如下:

  1. from contextlib import contextmanager  
  2.  
  3.    @contextmanager  
  4.    def context_illustration():  
  5.        print('entering context')  
  6.  
  7.        try:   
  8.            yield  
  9.        except Exception as e:  
  10.            print('leaving context')  
  11.            print('with an error (%s)' % e)  
  12.            # 需要再次抛出异常  
  13.            raise  
  14.        else:  
  15.            print('leaving context')  
  16.            print('with no error') 

如果出现任何异常,该函数都需要再次抛出这个异常,以便传递它。注意,context _ illustration在需要时可以有一些参数,只要在调用时提供这些参数即可。这个小的辅助函数简化了常规的基于类的上下文API,正如生成器对基于类的迭代器API的作用一样。

这个模块还提供了其他3个辅助函数。

closing(element):返回一个上下文管理器,在退出时会调用该元素的close方法。例如,它对处理流的类就很有用。

supress(*exceptions):它会压制发生在with语句正文中的特定异常。

redirect _ stdout(new _ target)和redirect _ stderr(new _ target):它会将代码块内任何代码的sys.stdout或sys.stderr输出重定向到类文件(file-like)对象的另一个文件。

喜欢的朋友可以添加我们的微信账号:

51CTO读书频道二维码


51CTO读书频道活动讨论群:365934973

【责任编辑:book TEL:(010)68476606】

回书目   上一节   下一节
点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

数据挖掘:概念与技术

本书第1版曾被KDnuggets的读者评选为最受欢迎的数据挖掘专著,是一本可读性极佳的教材。它从数据库角度全面系统地介绍了数据挖掘的基本概念...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊