前言本文重点介绍Python上下文管理器的讲解和应用。或者通过代码示例、比较理解和学习的方式,以达到“多、快、好、省”的理解、掌握和应用。闲话少说,开始——1.什么是上下文管理器上下文管理器是一个对象,它定义了with语句执行时要建立的运行时上下文。上下文管理器是自动处理进入和退出执行代码块的上下文所需的运行时。上下文管理器通常使用with语句调用,但也可以通过直接调用它们的方法来使用。上下文管理器的典型用途包括保存和恢复各种全局状态、锁定和解锁资源、关闭打开的文件等。在章节中,我们将学习如何在Python中使用上下文管理器以及如何自定义上下文管理器。1.with语句with语句用于执行上下文管理器定义的方法包装块。这允许封装常见的try...except...finally使用模式以便于重用。with语句提供了比传统的try...except...finally块更短且可重用的代码。在Python标准库中,很多类都支持with语句。一个非常常见的例子是内置的open()函数,它提供了一种使用with语句处理文件对象的模式。以下是with语句的一般语法:withexpressionastarget:#Usetarget#tohandlethings让我们看一个使用open()函数的例子。当前项目的文件夹中有一个文本文件。该文件名为color_names.txt,包含一些颜色名称(您可以自己提供一些文本内容)。我们要使用open()函数和with语句打开并打印此文件的内容。代码示例如下:importosfileDirPath=os.getcwd()+os.sep+"ctxManager"+os.sep#自定义文件路径#指定文件路径和名称path=fileDirPath+'files/color_names.txt'#with语句withopen(path,mode='r')asfile:#读取文件内容print(file.read())运行程序输出如下with语句的情况。我们使用open()函数打开给定路径(path)上的文件,open()函数以只读方式返回文件对象。然后在代码中使用这个文件对象,通过代码:print(file.read())读取并打印出它的内容。上面的例子是上下文管理器的典型用法。为了更好的理解和应用contextmanager,我们还得继续往下看。3.上下文管理器协议上下文管理器协议(ContextManagerProtocol),说白了就是上下文管理器的处理机制,或者说是一个预定的规范标准。这部分也可以在这里查看:Python核心协议。为了阅读的独立性,我在这里再说一遍。Python的with语句支持由上下文管理器定义的运行时上下文的概念。这是通过一对方法实现的,这些方法允许用户定义的类定义一个运行时上下文,该上下文在语句体执行之前进入并在语句结束时退出。上述方法称为上下文管理器协议。下面我们详细看一下这两个方法:1)__enter__(self)该方法由with语句调用,用于进入与当前对象关联的运行时上下文。with语句将此方法的返回值绑定到语句的as子句中指定的目标(如果有)。上例中返回的上下文管理器是一个文件对象。在幕后,文件对象从__enter__()返回自身以允许open()用作with语句中的上下文表达式。2)__exit__(self,exc_type,exc_value,traceback):当执行离开with代码块时调用该方法。它退出与此对象关联的运行时上下文。该参数描述了导致退出上下文的异常信息。如果上下文没有异常退出,那么所有三个参数都将为None。如果提供了异常,并且希望该方法抑制异常(即防止它被传播),那么它应该返回一个True值。否则,异常会在退出此方法时正常处理。__exit__()方法返回一个布尔值,可以是True或False。使用上下文管理器协议中的方法执行with语句的过程如下:withEXPRESSIONasTARGET:SUITE计算上下文表达式(EXPRESSION)以获得上下文管理器。加载上下文管理器的__enter__()以供后续使用。加载上下文管理器的__exit__()以供后续使用。调用上下文管理器的__enter__()方法。如果一个TARGET包含在with语句中,它被赋予__enter__()的返回值。执行套件(with语句范围内的代码块)。调用上下文管理器的__exit__()方法。如果异常导致套件退出,它的类型、值和回溯将作为参数传递给__exit__()。否则,提供三个None参数。如果套件因异常以外的任何原因退出,则__exit__()的返回值将被忽略,后续代码执行(如果有的话)将继续执行退出类型的正常位置。4.类形式的上下文管理器现在我们了解了上下文管理器协议背后的基本思想,让我们在类中实现它。这个类将成为我们的上下文管理器,稍后在with语句中使用它。定义的上下文管理器类参考列表如下:#自定义上下文管理器类classCustomContextManager:#初始化方法init->定义一些变量def__init__(self,path,mode):self.path=pathself.mode=modeself.file=None#__enter__方法->打开filedef__enter__(self):self.file=open(self.path,self.mode)returnself.file#exit方法关闭filedef__exit__(self,exc_type,exc_value,exc_traceback):self.file.close()我们的CustomContextManager类实现了成为上下文管理器的必要方法:__enter__和__exit__。在__init__方法中,它定义了三个实例变量来存储路径、模式和文件对象。在__enter__方法中,它使用内置的open()函数打开指定路径中的文件。由于open()函数返回一个文件对象,我们将其分配给self.file属性。在__exit__方法中,我们关闭文件:self.file.close()。__exit__方法接受上下文管理器协议所需的三个参数。现在我们可以在with语句中使用自定义上下文管理器。使用自定义类上下文管理器的示例(与我们之前的示例相同):#ApplicationexampleimportosfileDirPath=os.getcwd()+os.sep+"ctxManager"+os.sep#在with语句管理器中使用自定义上下文file_path=fileDirPath+'files/color_names.txt'withCustomContextManager(path=file_path,mode='r')asfile:#Outputfilecontentprint(file.read())运行的输出结果这里不再赘述。简单解释一下代码。在上面的列表中,CustomContexManager类在with语句中用于读取文件内容并打印出来。下面是这个自定义上下文管理器的幕后故事:1)在with行中,类CustomContextManager的_enter__方法被调用2)__enter__方法打开文件并返回它。3)我们将简单地命名打开的文件文件。4)在with语句块中,读取文件内容并打印出来。5)with语句自动调用__exit__方法。6)__exit__方法关闭文件。让我们定义另一个上下文管理器类。这次我们要打印指定文件夹中的文件列表。参考实现的代码清单如下:classContentList:'''打印目录的内容'''def__init__(self,directory):self.directory=directorydef__enter__(self):returnos.listdir(self.directory)def__exit__(self,exc_type,exc_val,exc_tb):ifexc_typeisnotNone:print("Errorgettingdirectorylist.")returnTrue#输出项目目录的内容project_directory='.'withContentList(project_directory)asdirectory_list:print(directory_list)在代码清单中,我们定义了一个新的上下文管理器。这个类的名称是ContentList。为什么它是上下文管理器?因为它实现了上下文管理器协议(__enter__和__exit__方法)。我们在类构造函数的__init__方法中将目录路径作为参数传递。在__enter__方法中,调用os模块中的listdir()方法即可得到目录中的内容列表:os.listdir(self.directory)。然后返回这个列表。请注意,我们在此上下文管理器中的__enter__方法返回一个列表。在__exit__方法中,我们检查是否有任何错误。如果我们的上下文管理器出现错误,exc_type、exc_val、exc_tb参数值就不会是None。所以我们检查exc_type是否为None来打印错误文本。在with语句中使用此上下文管理器。由于它返回一个列表对象,我们只需将返回值分配给directory_list变量。在with语句的主体中,我们打印了这个列表。在运行程序后的输出中,您可以看到项目目录的内容列表。记住,”。”表示当前目录,在我们的例子中是项目根目录(因项目环境不同,输出内容可能不同)。6.功能上下文管理器在上一篇文章中,我们学习了如何使用类语法定义上下文管理器。但这有点麻烦和冗长。因为需要显式实现__enter__和exit__方法,所以还需要处理可能的异常。所以我希望有更好的方法在Python中创建上下文管理器:基于函数的上下文管理器。事实上,函数上下文管理器是使用生成器和contextlib.contextmanager装饰器的特殊函数。contextlib.contextmanager装饰器负责实现上下文管理器协议。让我们定义一个功能上下文管理器。fromcontextlibimportcontextmanager#定义上下文管理器函数@contextmanagerdeffunction_based_context_manager():print("Enterthecontext:__enter__")yield"Thisisafunctionbasedonthecontextmanager"print("Leavethecontext:__exit__")#Usedwith语句中的上下文管理器函数withfunction_based_context_manager()asyield_text:print(yield_text)运行程序的输出类似如下:进入上下文:__enter__这是一个基于上下文管理器的函数离开上下文:__exit__在上面的代码中,我们定义了一个函数作为上下文管理器的自定义函数。contextmanager装饰器将常规函数转换为全栈上下文管理器(自动实现上下文管理器的协议)。如果为函数提供@contextmanager装饰器,则无需担心实现__enter__和__exit__函数。代码中的yield语句充当基于类的上下文管理器中__enter__方法中的return语句。因为我们使用了yield语句,所以这个基于函数的上下文管理器也是一个生成器函数。让我们定义一个新的上下文管理器。这次,它将以写入模式打开一个文件并添加一些文本。下面是一个示例:代码清单在清单中,我们定义了一个基于函数的上下文管理器。在try块中,它尝试打开指定路径中的文件,并指定了文件的默认编码集。如果它成功打开它,那么它会生成(返回)file_object。在finally块中,我们检查是否有要关闭的file_object。如果file_object不是None,则file_object被关闭。在with语句中,我们使用文件名funBasedContextManagers.txt调用上下文管理器。上下文管理器以写入模式打开文件并返回一个文件对象,我们将简单地命名为文件。然后在这个文件中写一些文本。请记住,如果这样的文件不存在,'w'模式将创建一个空文件。运行上面的程序,如果文件不存在,则生成对应名称的文件,并保留写入的内容。如果文件存在,这种写文件就是每次都写源文件的内容,请注意这个操作。使用上下文管理器来完成这种“收尾”工作非常方便,尤其是涉及到数据库操作时。比如可以自己包装一个来完成连接的自动关闭。本期小结本期我们介绍了上下文管理器的相关编程内容,如如何创建上下文管理器、上下文管理器协议、自定义类形式上下文管理器和函数上下文管理器等,大部分相关内容用实际代码进行演示和解释。如果你想提高你的编程技能,输入更多的代码是必不可少的要求。
