首页 > 编程语言 >Python上下文管理器with的用法

Python上下文管理器with的用法

时间:2023-03-18 17:44:16浏览次数:34  
标签:__ 管理器 Python self exit enter 上下文

  通常我们使用with关键字,作为上下文管理器进入标志。上下文管理器是一个包装任意代码块的对象,当退出上下文管理器时,保证相关的资源能够得到正确处理。最常用的用法是打开文件时使用上下文管理器,保证文件能被关闭,无论代码块中是否发生异常都会执行关闭的操作。例如下面的代码:

with open("上下文管理器/测试.txt","r", encoding='utf-8') as f:
    contents = f.readline()
    print(contents)

  上面代码中,我们使用Python的内置函数open(...)打开一个文件,并使用f这个引用来“接收返回的结果”,在上下文管理器中,我们读取了一行内容,并打印,最后退出上下文管理器。这是我们最常见的用法,这种用法保证了,即使在代码块(2-3行)中出现了异常,也能保证文件会被关闭,而不需要我们自己去手动关闭。因此上述代码其实是和下面的try-except-finally结构是一样的作用。

try:
    f = open("上下文管理器/测试.txt","r", encoding='utf-8')
    contents = f.readline()
    print(contents)
except:
    pass
finally:
    f.close()

1、编写自己的上下文管理器

  下面我们详细的讲解with到底发生了什么事情。其实with的本质是对with紧跟的代码进行求值(本例中就是调用open()函数),该表达式执行后(open()函数执行后)一定会返回一个对象,这个对象包含有两个魔法方法: __enter__和__exit__,并且会立刻执行这个对象的__enter__方法,__enter__方法的返回值会赋给as后面的变量(此例中就会赋给变量f)。我们使用下面的例子来详细解读:

## 定义一个上下文管理器类(包含有__enter__和__exit__方法)
class ContextManager(object):
    def __init__(self) -> None:
        self.entered = False

    def __enter__(self):
        print("__enter__方法被调用")
        self.entered = True
        return self
    
    def __exit__(self, *args):
        print("__exit__方法被调用")
        self.entered = False


with ContextManager() as f:
    print("f的entered属性:", f.entered)
    print("f的类型是:", type(f))
print("退出with后, f的entered属性:", f.entered)

  在这个例子中我们创造了一个上下文管理器类ContextManager,这个类具有 __enter__和__exit__方法。在第十六行,with后面的跟的是ContextManager(),也就是类的实例化操作,当完成类的实例化后得到了一个ContextManager对象,并且会立刻执行这个对象的__enter__方法(__enter__方法只有一个self参数),__enter__方法return了对象本身并赋值给了变量f。当程序执行完18行后,就退出了上下文管理器,此时就会可以执行ContextManager对象的__exit__方法。因此最后的程序执行结果如下:

  需要注意的是,with后面的表达式执行的结果(得到的对象)并没有赋值给任何变量,而f变量接收的实际上是执行__enter__方法执行的返回值。我们看下面这个例子:

class ContextManager(object):
    def __init__(self) -> None:
        self.entered = False

    def __enter__(self):
        self.entered = True
        return "__enter__返回的字符串"
    
    def __exit__(self, *args):
        self.entered = False


with ContextManager() as f:
    print("f的类型是: ", type(f))
    print("f为: ", f)

  执行结果是:

   我们在上一个例子的基础上,将__enter__方法的返回值改成了一个字符串,所以f变量实际上接收的就是__enter__方法的返回值的字符串。而with紧跟的表达式ContextManager()执行结果(得到一个ContextManager对象)并没有被任何变量接收。一般情况下__enter__方法都是返回self本身。

2、__exit__方法

  上一节中我们讲解了上下文管理器的基本流程,以及__enter__方法,本节将详细讲解在退出上下文管理器时发生的事情。其实退出上下文管理器就是在执行__exit__方法,__exit__方法接收三个位置参数(不包括传统的self参数)分别是:1、异常类型,2、异常实例,3、错误回溯信息。__exit__方法可以选择性的处理包装代码块(即with包裹的部分)中出现的异常,以及处理其他需要关闭上下文管理器状态的事情。__exit__方法会收集with代码块中抛出的异常(也就是位置参数的含义),如果__exit__方法返回True,则会终止异常抛出到程序,如果__exit__方法返回False,则会将捕获到的异常传播出去。我们从下面的代码中来理解:

class ContextManager(object):
    def __init__(self, x) -> None:
        self.x = x

    def __enter__(self):
        return self
    
    def __exit__(self, exc_type, exc_instance, traceback):
        if exc_instance:
            print(f"发生了{exc_type}异常,异常信息为: {exc_instance}")
        return True

with ContextManager("传递参数") as f:
    raise TypeError("with中抛出的异常")

  运行结果是:

  我们在with代码块中抛出了一个TypeError的异常,这个异常被__exit__方法捕获,并且进行了处理(即打印了一句话),由于__exit__方法返回的是一个True因此捕获到的异常不会被继续抛出,因此程序顺利结束了。如果我们将__exit__方法中返回值改为False,那么得到的运行结果就是下图了。

  可以看出__exit__方法虽然打印了错误信息(即执行了第10行),但由于__exit__方法返回值是False,因此with中的错误依然被抛出到了控制台。

  这里需要注意的是,如果with中已经有try-except对一些异常进行了处理,那么这些异常将不会交给__exit__方法处理。因此总结来说,__exit__方法会捕获with代码块中没有被处理的异常。__exit__方法的灵活性使得在方法内部可以完成以下操作:

  • 处理特定的异常。根据捕获的异常种类不同,决定是否将异常抛出(不同异常类型返回True或者False)
  • 基于属性来处理异常。其实上个例子中的13行,在with后面我们使用ContextManager(xxx)的方式创建了一个ContextManager对象,并传递给了给对象一个属性,这个属性值会被保存在ContextManager对象中,并且在__exit__方法中可以使用这个属性。因此我们可以根据不同的属性值,在退出with时进行不同的操作

3、总结

  上下文管理器提供了确保资源被正确处理的优秀方式,如果需要重复的使用同一个try-except-finally处理异常代码,那我们可以将其封装成上下文管理器。在__exit__方法中可以执行关闭资源的操作,这使得with代码块中出现异常时也能正确的关闭资源,通常这是非常重要的。

 

标签:__,管理器,Python,self,exit,enter,上下文
From: https://www.cnblogs.com/CircleWang/p/17231050.html

相关文章

  • 13 个非常有用的 Python 代码片段
    1:将两个列表合并成一个字典假设我们在Python中有两个列表,我们希望将它们合并为字典形式,其中一个列表的项作为字典的键,另一个作为值。这是在用Python编写代码时经常遇......
  • 100道python基础题——(6)
    编写一个程序,根据给定的公式计算并打印值:。以下是C和H的固定值:C是50。H是30。D是一个变量,它的值应该以逗号分隔的序列输入到程序中。例子假设程序的输入序列是逗号分隔的......
  • python爬取全国二手车数据,看看有没有可以捡漏的!
     絮叨一下话说,最近从湖北开始汽车价格战,全国人民都跑到湖北买车去了,搞得人家都出政策限制外地人购买了。不过12W的C6是真香呐,二十多万的C6一身毛病,12W的C6毛......
  • 100道python基础题——(5)
    Python简明教程---20,Python类中的属性与方法-码农充电站-博客园(cnblogs.com)问题:定义一个至少有两个方法的类:    getString:从控制台输入获取字符串......
  • python介绍
     二进制编码ASSIC每一个字符统一都需要8个bit来存储计算机容量1位=1bit8bit=1byte=1字节1024bytes=1kbytes=1KB1024个字符,小文档,几百k可以表示一张图片1024......
  • tensorrt官方案例 python运行
    1、案例数据下载1)-f配置案例的下载内容,会自动下载到案例文件夹中downloader.py-dD:/Programs/TensorRT-8.4.1.5/-f./yolov3_onnx/download.yml2、downloader.py中......
  • python elif的含义
    我一开始看成了else,结果是elif,两者出现的位置大概可能一样。解释如下:Python中elif表示再次判断的意思,是elseif的简写。elif语句不能独立使用,可以在需要检查更多条件时,与i......
  • python基础(8)
    1.集合内元素的三个特征。答:一是集合内元素都是不可变类型。二是集合内的元素是无序的。三是集合内的元素都是不重合的。2.集合的用途是什么?答:集合一般是整体运用,用来去重......
  • python安装robotframework的一些常见的错误
    python安装robotframework的一些常见的错误首先的电脑环境是x86的,然后下载的python版本起初是3.10.1的在cmd中出入pipinstallrobotframwork是没有问题的,但是在输入下......
  • python使用 pytesseract + tesseract-ocr 进行验证码识别
    使用pytesseract+tesseract-ocr进行验证码识别,需要安装的第三方库:pytesseract、tesseract-ocr,在使用pytesseract之前,必须安装tesseract-ocr,因为pytesseract依赖于t......