《流畅的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 自定义上下文管理器
下面我们自定义一个上下文管理器来说明上述四条解释: