zoukankan      html  css  js  c++  java
  • 理解Python的上下文管理器

    上下文管理器(context manager)是 Python 编程中的重要概念,用于规定某个对象的使用范围。一旦进入或者离开该使用范围,会有特殊操作被调用 (比如为对象分配或者释放内存)。它的语法形式是with...as...

    为了确保一些系统资源得以正确释放,我们经常会用到 try ... excepte ... finally 语句。如:

    try:
       f = open('somefile')
       for line in f:
           print(line)
    except Exception as e:
       print(e)
    finally:
        f.close()

    上面的代码模式,从用复用代码的模式来讲,并不够好。于是 with 语句出现了,通过定义一个上下文管理器来封装这个代码块:

    with open('filename', 'r') as f:
       for line in f:
              print(line)

    显然,with 语句比 try 语句简洁了许多。

    上下文管理器类

    由于 with 语句利用了上下文管理器,在深入理解 with 语句之前,我们先看看上下文管理器。我们要定义一个上下文管理器其实很简单,只要一个类实现了__enter__(self)和__exit__(self, exc_type, exc_valye, traceback)我们就叫他上下文管理器

    __enter__(self) 返回一个对象,可以是当前类的实例,也可以是其他对象。

    class SomeThing:
       def __enter__(self):
           return self        # 返回类实例
    
    class LineLength:
       def __init__(self, filepath):
           self.__file = open(self.__filepath)
    
       def __enter__(self):
           return self.__file        # 返回其他对象

    执行过程

    下面让我们看看 with 语句具体是如何执行的。

    第一步:执行上下文表达式以获得上下文管理器对象。上下文表达式就是 with 和 as 之间的代码。

    第二步:加载上下文管理器对象的 __exit__()方法备用。

    第三步:执行上下文管理器对象的__enter__()方法。

    第四步:将__enter__()方法返回值绑定到 as 后面的 变量中。

    第五步:执行 with 内的代码块。

    第六步:执行上下文管理器的__exit__()方法。

    如果在代码块中发生了异常,异常被传入__exit__()中。如果没有,__exit__()的三个参数会传入 None, None, None。__exit__()需要明确地返回 True 或 False。并且不能在__exit__()中再次抛出被传入的异常,这是解释器的工作,解释器会根据返回值来确定是否继续向上层代码传递异常。当返回 True 时,异常不会被向上抛出,代码会从报异常的代码处跳出上下文管理继续执行代码,当返回 False 时会向上抛出,阻断代码执行。当没有异常发生传入__exit__()时,解释器会忽略返回值。

    import time
    
    
    class File(object):
        def __init__(self, filename, mode):
            print("上下文管理执行顺序:
    1、执行初始化方法")
            self.f = open(filename, mode)
    
        def __enter__(self):
            print("2、执行__enter__()方法")
            return self     # "__enter__()方法的返回值绑定到 as 后面的 变量中
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            """
            :param exc_type:如果抛出异常,返回异常类型,否则返回None
            :param exc_val:如果抛出异常,返回异常内容,否则返回None
            :param exc_tb:如果抛出异常,返回异常位置,否则返回None
            :return:
            """
            print("4、执行__exit__()方法")
            if exc_type:
                print(exc_type, "
    ", exc_val, "
    ", exc_tb)
    
            # 若代码块报错则立即执行__exit__()方法中的代码;若代码块没报错,执行完代码块后执行__exit__()方法中的代码"
            self.f.close()
            time.sleep(1)
            # return True     # 当返回 True 时,异常不会被向上抛出,代码会从报异常的代码处跳出上下文管理继续执行代码,
            return False    # 当返回 False 时会在__exit__()方法执行完后向上抛出,阻断代码执行。
    
        def reads(self):
            return self.f.read()
    
    
    with File("README.md", "rb") as f:
        print("3、执行代码块代码")
        # 代码块部分
        f.reads()
        # 代码块报错,报错后的代码不会被执行
        print("若代码块报错,“我”不被执行;将r改为rb,代码块不报错“我”就执行")
    print("若代码块报错,__exit__()方法中返回 Ture “我”才执行;若代码块不报错,“我”也执行")

    执行结果:

    把"rb"改为"r"后执行结果:

     

    多上下文管理器

    实际上,我们可以同时处理多个上下文管理器:

    with A() as a, B() as b:
       suite

    所以我们大可不必写嵌套的 with 语句。

    contextmanager实现上下文管理器

    Python还提供了一个contextmanager的装饰器,更进一步简化了上下文管理器的实现方法。通过yield将函数分割成两部分,yield之前的部分相当于在__enter__方法中执行,yield之后的部分相当于在__exit__方法中执行,紧跟在yield之后的返回值给as之后的变量。

    from contextlib import contextmanager
    
    
    class File:
        def __init__(self, filename, mode):
            self.f = open(filename, mode)
    
        def reads(self):
            return self.f.read()
    
        def closed(self):
            self.f.close()
    
    
    @contextmanager
    def read_file():
        print(1)
        rf = File("README.md", "rb")
        yield rf
        print(3)
        rf.closed()
    
    
    with read_file() as f:
        print(2)
        f.reads()

    执行结果:

     把"rb"改为"r"后执行结果:

     注:当报错后,没有打印3,不清楚yield下面的代码是否执行。

  • 相关阅读:
    03 Python之变量以及常量介绍
    看女程序员是如何处理男友出轨,网友回复更精彩
    四面美团,收割 offer
    MySQL 优化实战记录
    龙岗一个月350的出租房,我搬出来了
    程序员工作 996 生病 ICU ?
    真的有人在偷听我们讲话么?
    一次非常有趣的 SQL 优化经历
    如何阅读Java源码?
    从 0 开始手写一个 Mybatis 框架,三步搞定!
  • 原文地址:https://www.cnblogs.com/testlearn/p/12434666.html
Copyright © 2011-2022 走看看