zoukankan      html  css  js  c++  java
  • 《Fluent Python》 CH.15_控制流程_上下文管理器和else块 (不仅而且的else作用、@contextmanager实现上下文管理器协议、建议yield 语句用try-finally进行保护)

    小结

    • 共计 61页

    本章速读

    • with 语句会设置一个临时的上下文,交给上下文管理器对象控制,内部包含 __enter__ 和 __exit__ 两个方法。
    • if-else的要么要么结构,for/else、while/else 和 try/else属于不仅而且结构,大大地不同。
    • @contextmanager 装饰器优雅且实用,把三个不同的 Python 特性结合 到了一起:函数装饰器、生成器和 with 语句。

    补充知识点

    Python用户往往会忽视或没有充分使用这些特性。

    下面要讨论的特性有:

    • with 语句和上下文管理器
    • for、while 和 try 语句的 else 子句

    with 语句会设置一个临时的上下文,交给上下文管理器对象控制,并 且负责清理上下文。这么做能避免错误并减少样板代码,因此 API 更安全,而且更易于使用。除了自动关闭文件之外,with 块还有很多用途。

    15.1 不仅而且, 不同于if-else的要么要么

    for/else、while/else 和 try/else 的语义关系紧密,不过与 if/else 差别很大。

    • for-else 仅当 for 循环运行完毕时(即 for 循环没有被 break 语句中止) 才运行 else 块。
    • while-else 仅当 while 循环因为条件为假值而退出时(即 while 循环没有被 break 语句中止)才运行 else 块。
    • try-else 仅当 try 块中没有异常抛出时才运行 else 块。官方文档 (https://docs.python.org/3/reference/compound_stmts.html)还指 出:“else 子句抛出的异常不会由前面的 except 子句处理。”

    在所有情况下,如果异常或者 return、break 或 continue 语句导致 控制权跳到了复合语句的主块之外,else 子句也会被跳过。

    我觉得除了 if 语句之外,其他语句选择使用 else 关键字是 个错误。else 蕴含着“排他性”这层意思,例如“要么运行这个循 环,要么做那件事”。可是,在循环中,else 的语义恰好相
    反:“运行这个循环,然后做那件事。”因此,使用 then 关键字更 好。then 在 try 语句的上下文中也说得通:“尝试运行这个,然后 做那件事。”可是,添加新关键字属于语言的重大变化,而 Guido 唯恐避之不及。 -- 作者
    else == then的字面意思作用

    在循环中使用 else 子句的方式如下述代码片段所示:

    for item in [1,2,3]:
        if item == 'banana':
            break
        else:
            raise ValueError('no banana flover found!')
    
    
    
    ---------------------------------------------------------------------------
    
    ValueError                                Traceback (most recent call last)
    
    <ipython-input-1-346a526ea195> in <module>
          3         break
          4     else:
    ----> 5         raise ValueError('no banana flover found!')
          6 
          7 
    
    
    ValueError: no banana flover found!
    

    示例,except-else

    复合语句的主块发生异常,跳过else

    try:
        1/0
    except OSError:
        print('OSError...')
    else:
        raise RuntimeError('non OSError...')
    
    ---------------------------------------------------------------------------
    
    ZeroDivisionError                         Traceback (most recent call last)
    
    <ipython-input-2-bd4a5d8c2e8b> in <module>
          1 try:
    ----> 2     1/0
          3 except OSError: print('OSError...')
          4 else: raise RuntimeError('non OSError...')
          5 
    
    
    ZeroDivisionError: division by zero
    

    复合语句的主块未发生异常

    try:
        1/1
    except OSError:
        print('OSError...')
    else:
        raise RuntimeError('non OSError...')
    
    
    ---------------------------------------------------------------------------
    
    RuntimeError                              Traceback (most recent call last)
    
    <ipython-input-3-99f2d3ada3c3> in <module>
          4     print('OSError...')
          5 else:
    ----> 6     raise RuntimeError('non OSError...')
          7 
          8 
    
    
    RuntimeError: non OSError...
    

    15.2 上下文管理器和with块

    上下文管理器对象存在的目的是管理 with 语句,就像迭代器的存在是 为了管理 for 语句一样。 with 语句的目的是简化 try/finally 模式。

    这种模式用于保证一段代 码运行完毕后执行某项操作,即便那段代码由于异常、return 语句或 sys.exit() 调用而中止,也会执行指定的操作。finally 子句中的代 码通常用于释放重要的资源,或者还原临时变更的状态。

    上下文管理器协议包含 __enter__ 和 __exit__ 两个方法。with 语句 开始运行时,会在上下文管理器对象上调用 __enter__ 方法。with 语 句运行结束后,会在上下文管理器对象上调用 __exit__ 方法,以此扮 演 finally 子句的角色。

    示例 15-1 with演示把文件对象当成上下文管理器使用

    with open('mirror.py') as fp:
        src = fp.read(60)
    
    
    ---------------------------------------------------------------------------
    
    FileNotFoundError                         Traceback (most recent call last)
    
    <ipython-input-4-b31d34c23dc8> in <module>
    ----> 1 with open('mirror.py') as fp:
          2     src = fp.read(60)
    
    
    FileNotFoundError: [Errno 2] No such file or directory: 'mirror.py'
    

    上下文管理器协议

    包含 enterexit 两个方法。with 语句 开始运行时,会在上下文管理器对象上调用 enter 方法。with 语 句运行结束后,会在上下文管理器对象上调用 exit 方法,以此扮演 finally 子句的角色。

    与函数和模块不同,with 块没有定义新的作用域。

    示例 15-3 是 LookingGlass 类的实现。

    class LookingGlass:
        def __enter__(self):
            import sys
            self.original_write = sys.stdout.write
            sys.stdout.write = self.reverse_write
            return 'JABBERWOCKY'
        def reverse_write(self, text):
            self.original_write(text[::-1])
        def __exit__(self, exc_type, exc_value, traceback):
            import sys
            sys.stdout.write = self.original_write
            if exc_type is ZeroDivisionError:
                print('Please DO NOT divide by zero!')
                return True
            print('__exit__')
    
    with LookingGlass() as what:
        print('hello')
        print('what')
    
    what
    
    olleh
    tahw
    __exit__
    
    
    
    
    
    'JABBERWOCKY'
    

    上下文管理器中的一些新的示例

    • 在 sqlite3 模块中用于管理事务
    • 在 threading 模块中用于维护锁、条件和信号
    • 为 Decimal 对象的算术运算设置环境,参见 decimal.localcontext 函数的文档
    • 为了测试临时给对象打补丁,参见 unittest.mock.patch 函数的 文档

    15.3 contextlib模块中的上下文管理工具

    closing

    如果对象提供了 close() 方法,但没有实现 enter/exit 协议,那么可以使用这个函数构建上下文管理器。

    suppress

    构建临时忽略指定异常的上下文管理器。

    @contextmanager

    这个装饰器把简单的生成器函数变成上下文管理器,这样就不用创 建类去实现管理器协议了。

    ContextDecorator

    这是个基类,用于定义基于类的上下文管理器。这种上下文管理器 也能用于装饰函数,在受管理的上下文中运行整个函数。

    ExitStack

    这个上下文管理器能进入多个上下文管理器。with 块结束 时,ExitStack 按照后进先出的顺序调用栈中各个上下文管理器的 exit 方法。如果事先不知道 with 块要进入多少个上下文管理器,可以使用这个类。例如,同时打开任意一个文件列表中的所有文件。

    15.4 使用@contextmanager

    @contextmanager 装饰器能减少创建上下文管理器的样板代码量,因为不用编写一个完整的类,定义 enterexit 方法,而只需实现有一个 yield 语句的生成器,生成想让 enter 方法返回的 值。

    示例 15-5 mirror_gen.py:使用生成器实现的上下文管理器

    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
        # 产出一个值,这个值会绑定到 with 语句中 as 子句的目标变量上。 执行 with 块中的代码时,这个函数会在这一点暂停。
        yield 'JABBERWOCKY'
        sys.stdout.write = original_write
    
    with looking_glass() as what:
        print('Alice, Kitty and Snowdrop')
        print(what)
    what
    
    pordwonS dna yttiK ,ecilA
    YKCOWREBBAJ
    
    
    
    
    
    'JABBERWOCKY'
    

    建议yield 语句用try-finally进行保护

    如果在 with 块中抛出了异常,Python 解释器会将其捕获,然后在 looking_glass 函数的 yield 表达式里再次 抛出。

    但是,那里没有处理错误的代码,因此 looking_glass 函数会 中止,永远无法恢复成原来的 sys.stdout.write 方法,导致系统处 于无效状态。

    使用 @contextmanager 装饰器时,要把 yield 语句放在 try/finally 语句中(或者放在 with 语句中),这是无法避免 的,因为我们永远不知道上下文管理器的用户会在 with 块中做什 么。

    示例 15-8 用于原地重写文件的上下文管理器

    import csv
    with inplace(csvfilename, 'r', newline='') as (infh, outfh):
        reader = csv.reader(infh)
        writer = csv.writer(outfh)
        for row in reader:
        row += ['new', 'columns']
        writer.writerow(row)
    

    inplace 函数是个上下文管理器,为同一个文件提供了两个句柄(这个 示例中的 infh 和 outfh),以便同时读写同一个文件。这比标准库中 的 fileinput.input 函数 (https://docs.python.org/3/library/fileinput.html#fileinput.input;顺便说一 下,这个函数也提供了一个上下文管理器)易于使用。

    你不逼自己一把,你永远都不知道自己有多优秀!只有经历了一些事,你才会懂得好好珍惜眼前的时光!
  • 相关阅读:
    LVDS DP等显示器接口简介
    Eclipse智能感知及快捷键
    Error deploying web application directory D:apache-tomcat-8.0.26webappsservlet
    把tomcat默认的8080段口改成80端口
    警告: [SetPropertiesRule]{Server/Service/Engine/Host/Context} Setting property 'source' to 'org.eclipse.jst.jee.server did not find a matching property.
    软件危机
    SQL Server——热备份
    mysql热备份
    Oracle——热备份
    Nodejs基础
  • 原文地址:https://www.cnblogs.com/zhazhaacmer/p/14465244.html
Copyright © 2011-2022 走看看