流畅的python,Fluent Python 第十五章笔记

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

 

简单写一下,其实一般自己写上下文管理器我觉的机会不多。

 

posted @ 2020-12-05 17:52  就是想学习  阅读(152)  评论(0编辑  收藏  举报