zoukankan      html  css  js  c++  java
  • 流畅的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装饰器时,默认的行为是相反的:如果压制了异常,不需要返回,如果不想压制异常,需要显式的抛出异常。

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

  • 相关阅读:
    WebStrom
    设计模式之6大原则
    tortoiseSVN 合并代码方法
    SpannableString属性详解
    TortoiseSVN设置比较工具为BeyondCompare
    Android 扩大view点击范围
    activity 与 fragment生命周期
    记录一个 spring cloud 配置中心的坑,命令行端口参数无效,被覆盖,编码集问题无法读取文件等.
    spring boot admin + spring boot actuator + erueka 微服务监控
    spring boot actuator 简单使用
  • 原文地址:https://www.cnblogs.com/sidianok/p/14090454.html
Copyright © 2011-2022 走看看