python 上下文管理器with块

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

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

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

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

最常见的例子是确保关闭文件对象。使用 with 语句关闭文件的详细说明参见如下示例:
在这里插入图片描述
解释:
1、f 绑定到打开的文件上,因为文件的 __enter__ 方法返回 self
2、从 f 中读取一些数据。
3、f 变量仍然可用,可以读取 f 对象的属性。
4、但是不能在 f 上执行 I/O 操作,因为在 with 块的末尾,调用TextIOWrapper.__exit__ 方法把文件关闭了。

执行 with 后面的表达式 得到的结果是上下文管理器对象,不过,把值绑定到目标变量上(as 子句)是在上下文管 理器对象上调用 __enter__ 方法的结果。

上面的示例中的 open() 函数返回 TextIOWrapper 类的实例,而该实例的 __enter__ 方 法返回 self。不过,__enter__ 方法除了返回上下文管理器之外,还可能返回其他对象

不管控制流程以哪种方式退出 with 块,都会在上下文管理器对象上调用 __exit__ 方法, 而不是在 __enter__ 方法返回的对象上调用。

with 语句的 as 子句是可选的。对 open 函数来说,必须加上 as 子句,以便获取文件的引用。不过,有些上下文管理器会返回 None,因为没什么有用的对象能提供给用户。

下面使用一个精心制作的上下文管理器执行操作,以此强调上下文管理器__enter__ 方法返回的对象之间的区别。

"""
上下文管理器
"""


class LookingGlass:

    # 除了 self 之外,Python 调用 __enter__ 方法时不传入其他参数。
    def __enter__(self):
        import sys
        # 把原来的 sys.stdout.write 方法保存在一个实例属性中,供后面使用
        self.original_write = sys.stdout.write
        # 为 sys.stdout.write 打猴子补丁,替换成自己编写的方法
        sys.stdout.write = self.reverse_write
        # 返回 'JABBERWOCKY' 字符串,这样才有内容存入目标变量 what
        return 'JABBERWOCKY'

    # 这是用于取代 sys.stdout.write 的方法,把 text 参数的内容反转,然后调用原来的实现。
    def reverse_write(self, text):
        self.original_write(text[::-1])

    # 如果一切正常,Python 调用 __exit__ 方法时传入的参数是 None, None, None;
    # 如果抛 出了异常,这三个参数是异常数据,如下所述。
    def __exit__(self, exc_type, exc_value, traceback):
        # 重复导入模块不会消耗很多资源,因为 Python 会缓存导入的模块。
        import sys
        # 还原成原来的 sys.stdout.write 方法。
        sys.stdout.write = self.original_write
        print('退出时,上下文管理器对象调用__exit__')
        # 如果有异常,而且是 ZeroDivisionError 类型,打印一个消息……
        if exc_type is ZeroDivisionError:
            print('Please DO NOT divide by zero!')
            #  ……然后返回 True,告诉解释器,异常已经处理了
            return True

        # 如果 __exit__ 方法返回 None,或者 True 之外的值,
        # with 块中的任何异常都会向上冒泡。


if __name__ == '__main__':
    # 上下文管理器是 LookingGlass 类的实例;
    # Python 在上下文管理器上调用 __enter__ 方 法,
    # 把返回结果绑定到 what 上。
    with LookingGlass() as what:
        # 打印一个字符串,然后打印 what 变量的值。
        # 打印出的内容是反向的。
        print('Alice, Kitty and Snowdrop')
        print(what)

    # 现在,with 块已经执行完毕。
    # 可以看出,__enter__ 方法返回的值——即存储在 what 变量中的值——是字符串 'JABBERWOCKY'
    print(what)
    # 输出不再是反向的了。
    print('Back to normal.')

运行结果:
在这里插入图片描述
在实际使用中,如果应用程序接管了标准输出,可能会暂时把 sys.stdout 换 成类似文件的其他对象,然后再切换成原来的版本。contextlib.redirect_ stdout 上下文管理器(https://docs.python.org/3/library/contextlib.html#contextlib. redirect_stdout)就是这么做的:只需传入类似文件的对象,用于替代sys. stdout

解释器调用 __enter__ 方法时,除了隐式的 self 之外,不会传入任何参数。传给 __exit__ 方法的三个参数列举如下:

#  异常类(例如 ZeroDivisionError)
exc_type

# 异常实例。有时会有参数传给异常构造方法,
# 例如错误消息,这些参数可以使用 exc_ value.args 获取。
exc_value

# traceback 对象
# 在 try/finally 语句的 finally 块中调用 sys.exc_info()
# (https://docs.python.org/3/library/sys.html#sys.exc_ info),
# 得到的就是 __exit__ 接收的这三个参数。
# 鉴于 with 语句是为了取代大多数 try/finally 语句, 
# 而且通常需要调用 sys.exc_info() 来判断做什么清理操作,这种行为是合理的。
traceback

上下文管理器的具体工作方式参见如下示例。在这个示例中,我们在 with 块之外使用 LookingGlass 类,因此可以手动调用 __enter____exit__ 方法。代码和调用如下:

"""
上下文管理器
"""


class LookingGlass:

    # 除了 self 之外,Python 调用 __enter__ 方法时不传入其他参数。
    def __enter__(self):
        import sys
        # 把原来的 sys.stdout.write 方法保存在一个实例属性中,供后面使用
        self.original_write = sys.stdout.write
        # 为 sys.stdout.write 打猴子补丁,替换成自己编写的方法
        sys.stdout.write = self.reverse_write
        # 返回 'JABBERWOCKY' 字符串,这样才有内容存入目标变量 what
        return 'JABBERWOCKY'

    # 这是用于取代 sys.stdout.write 的方法,把 text 参数的内容反转,然后调用原来的实现。
    def reverse_write(self, text):
        self.original_write(text[::-1])

    # 如果一切正常,Python 调用 __exit__ 方法时传入的参数是 None, None, None;
    # 如果抛 出了异常,这三个参数是异常数据,如下所述。
    def __exit__(self, exc_type, exc_value, traceback):
        # 重复导入模块不会消耗很多资源,因为 Python 会缓存导入的模块。
        import sys
        # 还原成原来的 sys.stdout.write 方法。
        sys.stdout.write = self.original_write
        print('退出时,上下文管理器对象调用__exit__')
        print(sys.exc_info())
        # 如果有异常,而且是 ZeroDivisionError 类型,打印一个消息……
        if exc_type is ZeroDivisionError:
            print('Please DO NOT divide by zero!')
            #  ……然后返回 True,告诉解释器,异常已经处理了
            return True

        # 如果 __exit__ 方法返回 None,或者 True 之外的值,
        # with 块中的任何异常都会向上冒泡。


if __name__ == '__main__':
    # 上下文管理器是 LookingGlass 类的实例;
    # Python 在上下文管理器上调用 __enter__ 方 法,
    # 把返回结果绑定到 what 上。
    # with LookingGlass() as what:
        # 打印一个字符串,然后打印 what 变量的值。
        # 打印出的内容是反向的。
        # print('Alice, Kitty and Snowdrop')
        # print(what)

    # 现在,with 块已经执行完毕。
    # 可以看出,__enter__ 方法返回的值——即存储在 what 变量中的值——是字符串 'JABBERWOCKY'
    # print(what)
    # 输出不再是反向的了。
    # print('Back to normal.')

    # 实例化并审查 manager 实例
    manager = LookingGlass()
    print(manager)
    # 在上下文管理器上调用 __enter__() 方法,把结果存储在 monster 中。
    monster = manager.__enter__()
    print(monster)
    #  monster 的值是字符串 'JABBERWOCKY'。
    #  打印出的 True 标识符是反向的,
    #  因为 stdout 的 所有输出都经过 __enter__ 方法中打补丁的 write 方法处理。
    print(monster == 'JABBERWOCKY')
    print(manager)
    # 调用 manager.__exit__,还原成之前的 stdout.write
    print(manager.__exit__(None, None, None))
    print(monster)



运行结果:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值