Python拾遗 上下文管理器和else语句 --流畅的python
本文的jupyter notebook文件在这里。
上下文管理器和else块
Python有一些其他语言中不常见的流程控制特性,因此也往往为人所忽视,本章讨论其中两个特性:
- with语句和上下文管理器
- for, while和try语句的else子句
if以外的else语句
我们习惯于if/else
语句,但是往往忽略,python中for, while, try
语句也能跟else
子句:
for
当循环运行完毕时(没被break
),才会运行else
块while
当循环因为条件为假而退出(没被break
),才会运行else
块try
当块中没有异常抛出时才会运行else
块
虽然说这里使用的关键词是else
,但是其实使用then
更符合其语义:先(成功)做这个,再做那个。
其中try
和else
的联合使用有些令人费解,毕竟else
块的代码可以放在try
里面:如果发生异常了无论是try
块剩下的部分还是else
部分的代码都不会被执行。这里的好处在于,可以明确try
语句防守的是哪些语句(哪些语句可能会抛出预期异常),使逻辑更明确。
一个for/else
的例子:
1 | my_list = ['apple', 'juice'] |
No banna found!
上下文管理器和with块
with语句的目的是简化try/finally模式,其中finally
子句常用于释放重要的资源。
with
语句开始运行时,会在上下文管理器对象上调用__enter__
方法;结束后会调用__exit__
方法。最常见的例子是关闭文件对象:
1 | with open('mirror.py', 'w') as fp: |
1 | fp |
<_io.TextIOWrapper name='mirror.py' mode='w' encoding='cp936'>
1
fp.write('aha')
1 | fp.write('aha') |
ValueError Traceback (most recent call last)
<ipython-input-6-cc89312ba692> in <module>()
----> 1 fp.write('aha')
ValueError: I/O operation on closed file.
这里我们看到fp
是一个TextIOWarpper
类的实例,这是因为open
函数返回该实例,而该实例的__enter__
方法返回self
。接着当with
块退出时,都会在上下文管理器对象上而不是__enter__
返回的对象调用__exit__
。
其中as
子句是可选的。
下面使用一个精心制作的例子来说明上下文管理器对象上和__enter__
返回的对象的区别。
1 | class LookingGlass: |
pordwonS dna yttiK ,ecilA
YKCOWREBBAJ
1 | what |
'JABBERWOCKY'
神奇的看到,在with
块中打印出来的值全都是反的,当退出with
块后,打印又恢复正常。
那么这个LookingGlass
上下文管理器是怎么做到的呢?
回到上面的代码,我们发现在__enter__
函数中,它将标准输出和逆转输出做了交换,接着返回了一个’JABBERWOCKY’并绑定到what
上。
那么在with
块中,所有调用print
(也就是sys.stdout.write
)的语句都变成调用reverse_write
。
接着在退出时,再将sys.stdout.write
恢复正常。注意这里的exc_type, exc_value, trackback
,当没有异常时,这里传入的参数都是None
;否则即是相关的异常数据。
contextlib模块中的实用工具
在自己定义上下文管理器之前,不妨可以看看能不能利用contextlib
模块中的工具。
closing
如果对象提供了close()
方法,但没有实现__enter__/__exit__
协议,那么可以 使用这个函数构建上下文管理器。suppress
构建临时忽略指定异常的上下文管理器。@contextmanager
这个装饰器把简单的生成器函数变成上下文管理器,这样就不用创建类去实现管理器 协议了。ContextDecorator
这是个基类,用于定义基于类的上下文管理器。这种上下文管理器也能用于装饰函数,在受管理的上下文中运行整个函数。ExitStack
这个上下文管理器能进入多个上下文管理器。with
块结束时,ExitStack
按照后进 先出的顺序调用栈中各个上下文管理器的__exit__
方法。如果事先不知道with
块要进 入多少个上下文管理器,可以使用这个类。例如,同时打开任意一个文件列表中的所有文件。
其中最常用的是@contextmanager
,下面就主要讨论以下该工具。
使用@contextmanager
使用@contextmanager
可以减少创建上下文管理器的样本代码量,因为不再需要定义__enter__
和__exit__
,只需要实现一个有yield
语句的生成器,并由此生成想让__enter__
返回的值。
简单的说,之前__enter__
协议的内容就写在yield
前面,__exit__
协议的内容写在yield
后面,yield
本身生成__enter__
的返回值。
以前面的例子来说明(省略异常处理模块):
1 | import contextlib |
pordwonS dna yttiK ,ecilA
YKCOWREBBAJ
1 | what |
'JABBERWOCKY'
csv
的inplace
是一个使用@contextmanager
构建上下文管理器的优秀用例,不妨前去学习参考。