第十五章 上下文管理器和else块
这是补的一块,前面看的时候,觉的用到的机会可能不多,就没写。
15.1讲了for,while,try结合else 的用法,这一块我已经掌握了,就不重复了。
书中的原句,在所有的情况下,如果异常或者return、break、或contiune语句导致控制权跳到了复合语句的主板之外,else字句也会被跳过。
书中最后介绍了一个有意思的玩意。
在Python中,try/except不仅用于处理错误,还常用于控制流畅。为此,Python官方词汇表还定义了一个缩略词(口号)。
EAFP
取得原谅比获得许可容易(easier to ask for forgiveness than permission)。这是一种常见的Python编程风格,先假定存在有效的键或属性,如果假定不成立,那么捕捉异常。这种风格简单明快,特点是代码中由很多try和except语句。与其他很多语言一样(如C语言),这种风格的对立面是LBYL风格
LBYL
三思而后行(look before you leap)。这种编程风格在调用函数或查找属性或键之前显式测试前提条件。与EAFP风格相反,这种风格的特点是代码中由很多if语句。在多线程环境中,LBYL风格可能会在"检查"和"行事"的空当引入条件竞争。列如,对if key in mapping: return mapping[key]这段代码来说,如果在测试之后,但在查找之前,另一个线程从映射中删除了那个键,那么这段代码就会失败。这个问题可使用锁或者EAFP风格解决。
15.2 上下文管理器和with块
with语句的目的是简化try/finally模式。这种模式用于保证一段代码运行完毕后执行某项操作,即便那段代码由于异常、return语句或sys.exit()调用而中支,也会执行指定的操作。finally子句中的代码通常用于释放重要的资源,或者还原临时变更的状态。
In [168]: with open(“exec_new3.sh”) as fp:
…: src = fp.read(20)
…: print(fp)
…:
…:
<_io.TextIOWrapper name=‘exec_new3.sh’ mode=‘r’ encoding=‘UTF-8’>
In [169]: len(src)
Out[169]: 20
In [170]: fp
Out[170]: <_io.TextIOWrapper name=‘exec_new3.sh’ mode=‘r’ encoding=‘UTF-8’>
In [171]: fp.closed,fp.encoding
Out[171]: (True, ‘UTF-8’)
In [172]: fp.read(10)
ValueError Traceback (most recent call last)
in
----> 1 fp.read(10)
ValueError: I/O operation on closed file.
with与函数和模块不同,with块没有定义新的作用域
In [173]: class LookingGlass:
…:
…: def enter(self):
…: import sys
…: self.original_write = sys.stdout.write
…: sys.stdout.write = self.reveres_write
…: return ‘JABBERWOCKY’
…:
…: def reveres_write(self, text):
…: return self.original_write(text[::-1])
…:
…: def exit(self, exc_type, exc_val, exc_tb):
…: import sys
…: sys.stdout.write = self.original_write
…: if exc_type is ZeroDivisionError:
…: print(“Please DO NOT divide by zero!”)
…: return True
…:
…:
In [174]: with LookingGlass() as what:
…: print(“Alice. kitty and Snowdrop”)
…: print(what)
…:
pordwonS dna yttik .ecilA
YKCOWREBBAJ
In [175]: what
Out[175]: ‘JABBERWOCKY’
In [176]: print(‘hello’)
hello
In [177]:
In [177]: with LookingGlass() as what:
…: print(“Alice. kitty and Snowdrop”)
…: print(what)
…: 1/0
…:
…:
pordwonS dna yttik .ecilA
YKCOWREBBAJ
Please DO NOT divide by zero!
In [178]: class LookingGlass:
…:
…: def enter(self):
…: import sys
…: self.original_write = sys.stdout.write
…: sys.stdout.write = self.reveres_write
…: return ‘JABBERWOCKY’
…:
…: def reveres_write(self, text):
…: return self.original_write(text[::-1])
…:
…: def exit(self, exc_type, exc_val, exc_tb):
…: import sys
…: sys.stdout.write = self.original_write
…: if exc_type is ZeroDivisionError:
…: print(“Please DO NOT divide by zero!”)
…:
…:
…:
In [179]: with LookingGlass() as what:
…: print(“Alice. kitty and Snowdrop”)
…: print(what)
…: 1/0
…:
…:
…:
pordwonS dna yttik .ecilA
YKCOWREBBAJ
Please DO NOT divide by zero!
ZeroDivisionError Traceback (most recent call last)
in
2 print(“Alice. kitty and Snowdrop”)
3 print(what)
----> 4 1/0
5
6
ZeroDivisionError: division by zero
这是书中的示范代码,在with的中通过猴子补丁修改sys.stdout的wirte方法。
这种我测试了最后一个重点,
如果__exit__方法返回None,或者True之外的值,with块中的任何异常都会向上冒泡。
In [2]: manager = LookingGlass()
In [3]: manager
Out[3]: <main.LookingGlass at 0x7fe17dfecfd0>
In [4]: monster = manager.enter()
In [5]: monster == “JABBERWOCKY”
Out[5]: eurT
In [6]: monster
Out[6]: ‘YKCOWREBBAJ’
In [7]: manager
Out[7]: >0dfcefd71ef7x0 ta ssalGgnikooL.niam<
In [9]: manager.exit(* [None] * 3)
In [10]: monster
Out[10]: ‘JABBERWOCKY’
In [11]: manager
Out[11]: <main.LookingGlass at 0x7fe17dfecfd0>
上面通过直接调用__enter__的方式运行,当调用__enter__就处于了该上下文环境中,通过__exit__退出设置的上下文环境。
15.4 使用@contextmanager
@contextmanager装饰器能减少创建上下文管理器的样板代码量,因为不用写一个完整的类,定义__enter__和__exit__方法,而只需实现有一个yield语句的生成器,生成向让__erter__方法返回的值。
在使用@contextmanager装饰的生成器中,yield语句的作用是把函数的定义体分成两部分;yield语句前面的所在代码在with块开始时(即解释器调用__enter__方法时)执行,yield语句后面的代码在with块结束时(既调用__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'
# 这个就好比__enter__, 下面的就是__exit__
sys.stdout.write = original_write
经过测试,这个在__exit__没有做处理,如果在with的条件下出错,那状态就一直维护在__enter__下的状态。
所以需要对这个函数进行改进
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
msg = ''
try:
yield 'JABBERWOCKY'
# 这个就好比__enter__, 下面的就是__exit__
except ZeroDivisionError:
msg = 'Please DO NOT divide by zero!'
finally:
sys.stdout.write = original_write
if msg:
print(msg)
这个是改进后的函数。
前面介绍的__exit__方法要返回True,此时解释器会压制异常。如果__exit__方法没有显式返回一个值,那么解释器得到的是None,然后向上冒泡异常。
使用@contextmanager装饰器时,默认的行为是相反的:如果压制了异常,不需要返回,如果不想压制异常,需要显式的抛出异常。