Python中的上下文管理器和else块

导语:本文章记录了本人在学习Python基础之控制流程篇的重点知识及个人心得,打算入门Python的朋友们可以来一起学习并交流。

本文重点:

1、掌握if语句之外的else的用法;

2、掌握上下文管理器的定义、协议、使用和with块;

3、掌握有用的@contextmanger装饰器。

一、if语句之外的else块

1、else块介绍

if/else中if和else是同级对立的语句,对立是指流程经过一层if/else语句只能对应一种处理语句。而else在for/else,while/else,try/else语句中的功能则截然不同。后者中的else功能如下:

  • for/else:for循环没有被break语句中止才运行else块。
  • while/else:while循环没有被break语句中止才运行else块。
  • try/else:try块中,没有异常抛出时才运行else块。

下面以for/else为例进行代码实现:

for i in "apple":
    if i.isupper():
        break
else:
    raise ValueError('No upper string was found')

2、try/else块背后的编程风格

try/except不仅用于处理错误,还用于处理错误,这属于EAFP编程风格。

  • EAFP:easier to ask for forgiveness than permission
    取得原谅比获得许可容易。即先假定存在有效的键或属性,如果假定不成立,那么捕获异常。
  • LBYL:look before you leap
    三思而后行。即在调用函数或查找属性或键之前显式测试前提条件。

二、上下文管理器和with块

1、上下文管理器介绍

上下文管理器(context manger):在操作文件和建立数据库连接的时候,我们最终需要关闭资源,这就是上下文管理器存在的意义。
上下文管理器协议:包含__enter__和__exit__两个方法。
语法:try/finally模式和with语句
实例:try/finally模式

try:
    f = open('test.txt', 'a+')
    f.write('Foo\n')
finally:
    f.close()

接下来我们用with语句进行替换:
实例:with语句

with open('test.txt', 'a+') as f:
    f.write('Foo\n')

分析:open 的返回值赋值给变量 f,当离开 with 代码块的时候,系统会自动调用 f.close() 方法。
总结:with块的功能在于简化try/finally模式。with语句在开始运行时会在上下文管理器对象上调用__enter__,而with语句结束时会调用__exit__方法。
Tips:with语句中的as语句是可选的,as语句将__enter__返回的值绑定到as语句后的变量。值得注意的是,对于open函数必须加上as字句来获取文件的引用。

2、自定义上下文管理器

在掌握基本上下文管理器和with语句后,我们通过自定义上下文管理器来深刻认识with语句和__enter__以及__exit__的联系。
实例:自定义满足上下文管理器协议的类

class OpenFileDemo(object):
    def __init__(self, filename):
        self.filename = filename
 
        self.f = open(self.filename, 'a+')
        return self.f
 
    def __exit__(self, exc_type, exc_value, traceback):
        self.f.close()
        if exc_type != SyntaxError:
            return True
       return False

with OpenFileDemo('test.txt') as f:
    f.write('Foo\n')

3、异常处理

当上下文管理器遇到异常时由__exit__方法处理。传给__exit__方法的三个参数如下:
exc_type:异常类(例如SyntaxError)。
exc_value:异常实例。有时会有参数传给异常构造方法,例如错误信息,可以使用exc_value.args获取这些参数。
traceback:traceback对象。

三、@contextmanger装饰器

@contextmanger装饰器是contextlib模块中的工具,它可以将包含yield的语句变成上下文管理器。
其中,yield之前的语句在__enter__方法中执行yield之后的语句在__exit__方法执行,yield后面的值是函数的返回值,绑定到实际调用的with中的as子句的目标变量上
如此可以避免编写一个类来实现上下文管理器协议。

实例:@contextmanger装饰器应用之计时器

import contextlib
import time

@contextlib.contextmanager
def timer():
    start=time.time()
    yield
    end=time.time()
    usedtime=end-start
    print('Running time was %r seconds'%usedtime)

with timer() as usedtime:
    time.sleep(1)

注意:一旦with块在调用timer出现异常时,抛出的异常会在timer函数中的yield表达式中再次抛出。如果timer函数没有处理异常的代码就会导致函数运行中止,系统处于无效状态。因此必要时在上下文管理器函数中使用try/finally语句防范错误。

@contextmanger集合了三个不同的Python特性:函数装饰器、生成器和with语句,非常实用!

四、contextlib模块中的实用工具

最后说明contextlib模块中包含的实用工具:

  • closing: 如果对象提供了 close() 方法,但没有实现 _enter__/__exit_ 协议,那么可以使用这个函数构建上下文管理器。
  • suppress: 构建临时忽略指定异常的上下文管理器。
  • @contextmanager: 这个装饰器把简单的生成器函数变成上下文管理器,这样就不用创建类去实现管理器协议了。
  • ContextDecorator: 这是个基类,用于定义基于类的上下文管理器。这种上下文管理器也能用于装饰函数,在受管理的上下文中运行整个函数。
  • ExitStack: 这个上下文管理器能进入多个上下文管理器。with 块结束时,ExitStack 按照后进先出的顺序调用栈中各个上下文管理器的_exit_ 方法。如果事先不知道 with 块要进入多少个上下文管理器,可以使用这个类。例如,同时打开任意一个文件列表中的所有文件。

显然,在这些实用工具中,使用最广泛的是 @contextmanager 装饰器,因此要格外留心。这个装饰器也有迷惑人的一面,因为它与迭代无关,却要使用 yield 语句。

    原文作者:Hanwencheng
    原文地址: https://segmentfault.com/a/1190000013505957
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞