Do This, Then That: else Blocks Beyond if
else
除了用在 if
/ else
块中,还可以跟 for
/ while
/ try
一起使用。
在 for
/ while
后使用,表示循环正常退出:
for item in my_list:
if item.flavor == 'banana':
break
else:
raise ValueError('No banana flavor found!')
在 try
后使用,表示没有异常抛出。对比这两段代码:
# Code 1
try:
dangerous_call()
after_call()
except OSError:
log('OSError...')
# Code 2
try:
dangerous_call()
except OSError:
log('OSError...')
else:
after_call()
# 这两段代码功能基本一致,但是第二段代码在表义上更清晰
try/except
这种模式,体现了 Python 更喜欢 EAFP (easier to ask for forgiveness than permission),而不是传统 C 风格的 LBYL (look before you leap)。传统 C 风格的程序需要很多 if
来判断操作成功了没,很啰嗦,比如:
int main(int argc, char *argv[])
{
int fd;
fd = open(argv[1], O_RDONLY);
if (-1 == fd) {
printf("\n open() failed with error [%s]\n",strerror(errno));
return 1;
}
// ...
}
Context Managers and with Blocks
with
语句,是用来简化 try/finally
pattern 的,一般用来做资源自动释放。
context manager 跟 with
搭配使用,主流有两种写法,一种基于类,一种用函数。
class LookingGlass:
def __enter__(self):
import sys
self.original_write = sys.stdout.write
sys.stdout.write = self.reverse_write
# 在 __enter__ 中返回的内容,会被成 as 后的值
return 'JABBERWOCKY'
def reverse_write(self, text):
self.original_write(text[::-1])
def __exit__(self, exc_type, exc_value, traceback):
# sys.exc_info() 返回的内容即是 (exc_type, exc_value, traceback),
# 这不是巧合,因为大家经常在 finally 块中调用 sys.exc_info() 来判断做什么 clean-up
import sys
sys.stdout.write = self.original_write
if exc_type is ZeroDivisionError:
print('Please DO NOT divide by zero!')
return True # 返回 True 表示异常已经被处理好。否则异常还会被抛出。
>>> from mirror import LookingGlass
>>> with LookingGlass() as what:
... print('Alice, Kitty and Snowdrop')
... print(what)
...
pordwonS dna yttiK ,ecilA
YKCOWREBBAJ
>>> what
'JABBERWOCKY'
>>> print('Back to normal.')
Back to normal.
# 第二种写法,用函数,yield 前的类似 __enter__,后面类似 __exit__
import contextlib
@contextlib.contextmanager
def looking_glass():
import sys
original_write = sys.stdout.write
def reverse_write(text):
original_write(text[::-1])
sys.stdout.write = reverse_write
yield 'JABBERWOCKY'
sys.stdout.write = original_write
上面的函数写法有个问题,一旦 with
块里抛异常,就会通过 yield 继续抛出。把 yield
包上 try
块可以解决:
@contextlib.contextmanager
def looking_glass():
import sys
original_write = sys.stdout.write
def reverse_write(text):
original_write(text[::-1])
sys.stdout.write = reverse_write
msg = ''
try:
yield 'JABBERWOCKY'
except ZeroDivisionError:
msg = 'Please DO NOT divide by zero!'
finally:
sys.stdout.write = original_write
if msg:
print(msg)
同时,在 yield
周围包上 try/finally
是一种最佳实践,因为你不知道使用者会在 with 块中做什么。
Future Reading
又有很多好东西。如果要做分享讲 with
的特性,可以从里面挖掘材料。