用 Python 做一件很平常的事情: 打开文件, 逐行读入, 最后关掉文件; 进一步的需求是, 这也许是程序中一个可选的功能, 如果有任何问题, 比如文件无法打开, 或是读取出错, 那么在函数内需要捕获所有异常, 输出一行警告并退出. 代码可能一开始看起来是这样的
不过这显然无法运作, 因为
f
是在
try
块中定义的, 而在
finally
中无法引用.
如果将 f 提取到 try 块外部, 如
那么, 问题在于当打开文件失败, 抛出异常将不会被捕获.
挫一点的方法自然是, 再套一层 try 吧
当然这不仅仅是多一层缩进挫了, 连警告输出都白白多一次呢.
正规一点的方式是, 使用 Python 引入的 with 结构来解决, 如
当文件打开失败时, 异常自然会被
except
到; 否则, 在
with
块结束之后, 打开的文件将自动关闭.
除了打开文件, 还有其它这样可以用于 with 的东西么? 或者说, 怎么自定义一个什么东西, 让它能用于 with 呢?
直接回答后一个问题吧, 秘密在于 Python 虚拟机在 with 块退出时会去寻找对象的 __exit__ 方法并调用它, 把释放资源的动作放在这个 __exit__ 函数中就可以了; 另外, 对象还需要一个 __enter__ 函数, 当进入 with 块时, 这个函数被调用, 而它的返回值将作为 as 后引用的值. 一个简单的例子是
执行这一段代码, 输出将会是
__exit__
函数接受三个参数, 分别是异常对象类型, 异常对象和调用栈. 如果
with
块正常退出, 那么这些参数将都是
None
. 返回
True
表示发生的异常已被处理, 不再继续向外抛出.
很简单直观,多个线程共用一个优先级队列的时候,首先先用互斥锁lock.acquire()把优先级队列锁上,然后取元素,再然后lock.release()释放这个锁。
虽然看似非常符合逻辑的一个过程,但是里面隐藏着一个巨大的bug:当heap里面没有元素的时候,会抛出一个IndexError异常,再然后堆栈回滚,再然后lock.release()根本不会执行,这个锁就永远得不到释放,因此就发生了喜闻乐见的死锁问题。这个也是很多大神们讨厌异常的原因。经典Java风格的解决方案就是
这个虽然可以,但是怎么看怎么dirty,和Python优雅、简单的风格出入很大。其实,自从Python2.5开始引入了with语句,一切就变得非常简单:
在此无论以何种方式离开with语句的代码块,锁都会被释放。
with语句的设计目的就是为了使得之前需要通过try...finally解决的清理资源问题变得简单、清晰,它的的用法是
其中expression返回一个叫做「context manager」的对象,然后这个对象被赋给variable(如果有的话)。「context manager」对象有两个方法,分别是__enter__()和__exit__(),很明显一个在进入with-block时调用,一个离开with-block的时候调用。
无论是正常离开还是因为异常原因离开with语句块,打开的文件资源总是会释放。
接下去讨论一下with语句配合contextlib库的一些比较实用的方法,比如需要同时打开两个文件,一个读一个写,这个时候就可以这样写:
这样就可以省掉两个with的语句的嵌套了,另外如果遇到一些还没有支持「context manager」的API呢?比如urllib.request.urlopen(),这个返回的对象因为不是「context manager」,结束的时候还需要自己去调用close方法。
类似这种API,contextlib提供了一个叫做closing方法,它会在离开with语句的时候,自动调用对象的close方法,因此urlopen也可以这样写:
1
2
3
4
5
6
7
8
|
def
read_file():
try
:
f
=
open
(
'yui'
,
'r'
)
print
''.join(f.readlines())
except
:
print
'error occurs while reading file'
finally
:
f.close()
|
如果将 f 提取到 try 块外部, 如
1
2
3
4
5
6
7
8
|
def
read_file():
f
=
open
(
'azusa'
,
'r'
)
try
:
print
''.join(f.readlines())
except
:
print
'error occurs while reading file'
finally
:
f.close()
|
挫一点的方法自然是, 再套一层 try 吧
1
2
3
4
5
6
7
8
9
10
11
|
def
read_file():
try
:
f
=
open
(
'sawako'
,
'r'
)
try
:
print
''.join(f.readlines())
except
:
print
'error occurs while reading file'
finally
:
f.close()
except
:
print
'error occurs while reading file'
|
正规一点的方式是, 使用 Python 引入的 with 结构来解决, 如
1
2
3
4
5
6
|
def
readFile():
try
:
with
open
(
'mio'
,
'r'
) as f:
print
''.join(f.readlines())
except
:
print
'error occurs while reading file'
|
除了打开文件, 还有其它这样可以用于 with 的东西么? 或者说, 怎么自定义一个什么东西, 让它能用于 with 呢?
直接回答后一个问题吧, 秘密在于 Python 虚拟机在 with 块退出时会去寻找对象的 __exit__ 方法并调用它, 把释放资源的动作放在这个 __exit__ 函数中就可以了; 另外, 对象还需要一个 __enter__ 函数, 当进入 with 块时, 这个函数被调用, 而它的返回值将作为 as 后引用的值. 一个简单的例子是
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
class
Test:
def
__init__(
self
):
print
'init'
def
__enter__(
self
):
print
'enter'
return
self
def
__exit__(
self
, except_type, except_obj, tb):
print
except_type
print
except_obj
import
traceback
print
''.join(traceback.format_tb(tb))
print
'exit'
return
True
with Test() as t:
raise
ValueError(
'kon!'
)
|
1
2
3
4
5
6
7
8
|
init
enter
<
type
'exceptions.ValueError'
>
kon!
File
"test.py"
, line
17
,
in
<module>
raise
ValueError(
'kon!'
)
exit
|
简单的介绍到此为止, 详细的情况可以参考 PEP 343 (这数字真不错, 7 3 ).
下面介绍下 with 语句的实例用法 & 高级用法:
Python高端、大气、上档次的with语句
在说with语句之前,先看看一段简单的代码吧
1
2
3
4
5
|
lock
=
threading.Lock()
...
lock.acquire()
elem
=
heapq.heappop(heap)
lock.release()
|
虽然看似非常符合逻辑的一个过程,但是里面隐藏着一个巨大的bug:当heap里面没有元素的时候,会抛出一个IndexError异常,再然后堆栈回滚,再然后lock.release()根本不会执行,这个锁就永远得不到释放,因此就发生了喜闻乐见的死锁问题。这个也是很多大神们讨厌异常的原因。经典Java风格的解决方案就是
1
2
3
4
5
6
7
|
lock
=
threading.Lock()
...
lock.acquire()
try
:
elem
=
heapq.heappop(heap)
finally
:
lock.release()
|
1
2
3
4
|
lock
=
threading.Lock()
...
with lock:
elem
=
heapq.heappop(heap)
|
with语句的设计目的就是为了使得之前需要通过try...finally解决的清理资源问题变得简单、清晰,它的的用法是
1
2
|
with expression [as variable]:
with
-
block
|
这样的对象不需要自己去实现,在Python标准库里面很多API都是已经实现了这两个方法,最常见的一个例子就是读写文件的open语句。
1
2
|
with
open
(
'1.txt'
, encoding
=
'utf-8'
) as fp:
lines
=
fp.readlines()
|
接下去讨论一下with语句配合contextlib库的一些比较实用的方法,比如需要同时打开两个文件,一个读一个写,这个时候就可以这样写:
1
2
3
4
|
from
contextlib
import
nested
...
with nested(
open
(
'in.txt'
),
open
(
'out.txt'
,
'w'
)) as (fp_in, fp_out):
...
|
类似这种API,contextlib提供了一个叫做closing方法,它会在离开with语句的时候,自动调用对象的close方法,因此urlopen也可以这样写:
1
2
3
4
5
|
from
contextlib
import
closing
...
with closing(urllib.request.urlopen(
'http://www.yahoo.com'
)) as f:
for
line
in
f:
sys.stdout.write(line)
|