十五章

第十五章 上下文管理器和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装饰器时,默认的行为是相反的:如果压制了异常,不需要返回,如果不想压制异常,需要显式的抛出异常。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值