• 强大的with语句


    上下文管理器对象存在的目的是管理 with 语句,就像迭代器的存在是为了管理 for 语句一样。

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

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

    ==执行 with 后面的表达式得到的结果是上下文管理器对象,把值绑定到目标变量上(as 子句)是在上下文管理器对象上调用enter方法的结果==。with 语句的 as 子句是可选的。对 open 函数来说,必须加上 as子句,以便获取文件的引用。不过,有些上下文管理器会返回 None,因为没什么有用的对象能提供给用户。

    1
    2
    with open('mirror.py') as fp:
      ...

    自定义的上下文类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class A:
      def __init__(self, name):
        self.name = name
     
      def __enter__(self):
        print('enter')
        return self.name
     
      def __exit__(self, exc_type, exc_val, exc_tb):
        print('gone')
     
    with A('xiaozhe') as dt:
      print(dt)

    contextlib模块

    contextlib 模块中还有一些类和其他函数,使用范围更广。

    closing:如果对象提供了 close() 方法,但没有实现enter/exit协议,那么可以使用这个函数构建上下文管理器。
    suppress:构建临时忽略指定异常的上下文管理器。
    @contextmanager:==这个装饰器把简单的生成器函数变成上下文管理器==,这样就不用创建类去实现管理器协议了。
    ContextDecorator:这是个基类,用于定义基于类的上下文管理器。这种上下文管理器也能用于装饰函数,在受管理的上下文中运行整个函数
    ExitStack:这个上下文管理器能进入多个上下文管理器。 with 块结束时, ExitStack 按照后进先出的顺序调用栈中各个上下文管理器的exit方法。

    ==使用最广泛的是 @contextmanager 装饰器,因此要格外留心。这个装饰器也有迷惑人的一面,因为它与迭代无关,却要使用 yield 语句==。

    使用@contextmanager

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

    在使用 @contextmanager 装饰的生成器中, yield 语句的作用是把函数的定义体分成两部分: ==yield 语句前面的所有代码在 with 块开始时(即解释器调用enter方法时)执行, yield 语句后面的代码在 with 块结束时(即调用exit方法时)执行==。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import contextlib
     
    @contextlib.contextmanager
    def test(name):
      print('start')
      yield name
      print('end')
     
    with test('zhexiao123') as dt:
      print(dt)
      print('doing something')

    实现原理

    contextlib.contextmanager 装饰器会把函数包装成实现enter和exit方法的类。类的名称是 _GeneratorContextManager。

    这个类的enter方法有如下作用: 
    1. 调用生成器函数,保存生成器对象(这里把它称为 gen)。 
    2. 调用 next(gen),执行到 yield 关键字所在的位置。 
    3. 返回 next(gen) 产出的值,以便把产出的值绑定到 with/as 语句中的目标变量上。

    with 块终止时,exit方法会做以下几件事: 

    1. 检查有没有把异常传给 exc_type;如果有,调用 gen.throw(exception),在生成器函数定义体中包含 yield 关键字的那一行抛出异常。 
    2. 否则,调用 next(gen),继续执行生成器函数定义体中 yield 语句之后的代码。

    异常处理

    为了告诉解释器异常已经处理了,exit方法会返回 True,此时解释器会压制异常。如果exit方法没有显式返回一个值,那么解释器得到的是 None,然后向上冒泡异常。

    使用 @contextmanager 装饰器时,默认的行为是相反的:装饰器提供的exit方法假定发给生成器的所有异常都得到处理了,因此应该压制异常。 如果不想让 @contextmanager 压制异常,必须在被装饰的函数中显式重新抛出异常。

    上面的代码有个bug:如果在 with 块中抛出了异常, Python 解释器会将其捕获,然后在 test 函数的 yield 表达式里再次抛出。但是,那里没有处理错误的代码,因此 test 函数会中止。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import contextlib
     
    @contextlib.contextmanager
    def test(name):
      print('start')
     
      try:
        yield name
      except:
        raise ValueError('error')
      finally:
        print('end')
     
    with test('zhexiao123') as dt:
      print(dt)
      print('doing something')

     
  • 相关阅读:
    centos出现“FirewallD is not running”怎么办
    Centos7开放及查看端口
    phpRedis函数使用总结
    如何在Windows的PHPstudy中使用redis
    Redis命令操作详解
    不可不知 DDoS的攻击原理与防御方法
    DDoS 攻击与防御:从原理到实践
    ORM Integrity constraint violation: 1052 Column 'id' in where clause is ambiguous
    PHP常用函数大全500+
    Linux彻底卸载Nginx
  • 原文地址:https://www.cnblogs.com/ellisonzhang/p/10473447.html
Copyright © 2020-2023  润新知