专栏名称: VPointer
软件开发
目录
相关文章推荐
北京商报  ·  315晚会曝光“问题虾仁”已下架!此前头部主 ... ·  18 小时前  
环球网  ·  3·15曝光“保水虾仁”,磷酸盐超标! ·  20 小时前  
环球网  ·  3·15曝光“保水虾仁”,磷酸盐超标! ·  20 小时前  
中国药闻  ·  中国经济航船必将乘风破浪、行稳致远 ·  2 天前  
51好读  ›  专栏  ›  VPointer

Python学习之路33-上下文管理器和else块

VPointer  · 掘金  ·  · 2018-07-24 02:17

正文

阅读 23

Python学习之路33-上下文管理器和else块

《流畅的Python》笔记。

本篇主要讨论Python用户常忽略掉的一些流程控制特性,包括上下文管理器和else块。内容包括else与非if关键字的搭配;Python中的上下文管理器,如何自定义上下文管理器,以及contextlib模块中@contextmanager装饰器的用法。

1. if语句之外的else块

else 除了和 if 搭配之外,在Python中,它还能与 for while try 搭配:

  • for :仅当 for 循环运行完毕时才运行 else
  • while :仅当 while 循环因为条件为假而退出时才运行 else
  • try :仅当 try 块中没有抛出异常时才运行 else 块,且 else 块中抛出的异常不会被前面的 except 子句处理
  • 在上述三个情况中,如果异常、 return break continue 语句导致控制权跳到了复合语句的主块之外, else 子句会被跳过。

在这些语句中使用 else 字块有事能让代码更易读,而且能省去一些麻烦,不用设置控制标志或者添加额外的 if 语句,尤其是在和 try 复合时。 try 块中的代码应该只含有预计会抛出异常的语句,以下是两种写法的对比:

# 代码1.1,只有dangerous_all()可能会抛出异常
# 写法1
try:
    dangerous_all()
    after_call()
except OSError:
    log("OSError...")

# 写法2,此写法比上述写法更明确
try:
    dangerous_all()
except OSError:
    log("OSError...")
else:   # 但其实这么写也是多余的
    after_call()
复制代码

但是, 并不建议大家在这些关键字后面加 else ,因为这很容易造成歧异,比如笔者第一眼看到 for/else 时的理解是:如果不能进入 for 块,则运行 else 中的内容,但实际刚好相反。在其他语言中,此时的 else 一般由关键字 then 代替,但Python的创建人非常讨厌添加新关键字,所以让 else 担起了这个职责。许多编程规范的书中也不建议在这些关键字后面添加 else 块。

***补充:***在Python中, try/except 不仅用于错误处理,还和 if/else 一样,常用于控制流程,因此,这就形成了两种代码风格:

  • EAFP:“取得原谅比获得许可更容易”(Easier to Ask for Forgiveness than Permission),通俗讲就是“不管会不会抛异常,先运行再说,等抛出了异常再处理”,这种风格的特点就是代码中有很多 try/except 块;
  • LBYL:“三思而后行”(Look Before You Leap),这种风格就是显式测试前提条件,通俗讲就是“必须合规后才能运行”,这种风格的特点就是代码中有很多 if/else 块。

2. 上下文管理器和with块

说到 上下文管理器 ,那首先就得说说什么是 上下文 。笔者第一次接触这个概念的时候很费解,笔者是按语文里的概念来理解的:不就是前一句话后一句话,前一段话后一段话吗,这有什么可管理的?虽然至今笔者也没看到关于“上下文”这个概念的准确定义,但用多了之后,大致能理解为:

某段代码B将整个程序分成了3段,从前到后分别为A,B,C。当运行代码段B时,程序运行环境的某些设定需要发生改变;当退出代码段B后,这些被改变的设置需恢复原样,即保持A和C的一致性。A和B,B和C就称之为 上下文 。由于某些原因(如程序员大意、抛出异常强制退出等),B中所改变的设置并不总能手动恢复回去,所以,通常将这些设置交由某些对象统一管理,这些对象就叫做 上下文管理器

2.1 Python中的上下文管理器

上下文管理器采用的是鸭子类型技术,实现了 __enter__ __exit__ 两个抽象方法的对象就是上下文管理器。

上下文管理器对象的存在目的是为了管理 with 语句,而 with 语句的目的是简化 try/finally 模式。

with 块的经典用法之一就是读写文件:

# 代码2.1
>>> with open("text.txt") as fp:  # 变量fp还有一个称呼,叫"句柄"
...      pass
...
>>> fp
<_io.TextIOWrapper name="text.txt" mode="r" encoding="UTF-8">
复制代码

解释

  • with 后面的表达式(不包括 as 部分)得到的结果就是一个上下文管理器。此处 open() 函数返回了一个 TextIOWrapper 对象,Python解释器会临时保存这个对象,我们这里将其取名为 a
  • with 语句块中,Python得到上下文管理器后会首先调用它的 __enter__ 方法,如果 with 后面跟了 as 关键字,则该方法的返回值会赋给 as 后面的变量。上述代码中,当Python得到了 a 后,调用它的 __enter__ 方法,该方法返回 a 对象自身( return self ),然后变量 fp 接收这个值。但请注意, 并不是所有的上下文管理器的 __enter__ 都返回实例自身
  • 当退出 with 块时,Python会调用 上下文管理器 __exit__ 方法,做最后处理。上述代码中,Python并不是调用 fp.__exit__() ,而是调用 a.__exit__()
  • 与函数和模块不同, with 块没有定义新的作用域,所以即便退出了 with 块,变量 fp 依然存在。

2.2 自定义上下文管理器

下面我们自定义一个上下文管理器来说明上述四条解释:







请到「今天看啥」查看全文