📜  用于包装任意代码块的可重用Python功能: Python上下文管理器

📅  最后修改于: 2022-05-13 01:55:12.375000             🧑  作者: Mango

用于包装任意代码块的可重用Python功能: Python上下文管理器

上下文管理器是包装任意(自由格式)代码块的工具。使用上下文管理器的主要原因之一是资源清洁度。上下文管理器确保进程在进入和退出时稳定执行,它释放资源。即使包装的代码引发异常,上下文管理器也会保证退出。因此,没有任何拖延,让我们潜入并获得资源清洁的新口头禅,而无需重复代码。

上下文管理器

上下文管理器

注意:感谢有关装饰器和生成器的知识

上下文管理器语法

众所周知,释放资源的正确方法是在使用后关闭资源。构建关闭函数的最常见做法是使用异常处理。让我们看看下面的代码。

def main():
    try:
        file = open('sample.txt', 'r')
        data = file.read()
        print(data)
    finally:
        file.close()
  
main()

finally 子句确保文件将在任何情况下关闭。但是这个过程会导致代码重复。那么,如何在没有样板设置的情况下使用上下文管理器来实现相同的目标。让我们一一浏览以下部分:

  • with 语句
  • 进出方法
  • 异常处理

with 语句

使用语句, Python内置函数可以用作上下文管理器。

with open (‘filename’, ’r’)  as file:
    data = file.read()

此代码执行的功能与我们使用异常处理开发的功能相同。这里的 with 语句应该返回一个带有两个魔术方法的对象:__enter__ 和 __exit__。 __enter__ 方法返回的结果被赋值给 `as` 关键字后面提到的变量。

处理包装代码中是否引发异常,最重要的是该语句确保资源被释放。因此,程序员不需要执行关闭操作。这是完整的代码。

def main():
    with open('sample.txt', 'r') as file:
        data = file.read()
        print(data)
  
main()

进出方法

__enter__ 方法返回一个对象,然后将返回的值赋给 `as` 关键字后面提到的变量。除了 self 参数之外,它不需要其他参数。
另一方面, __exit__ 方法接受除 self 参数之外的 3 个位置参数。默认情况下,这三个参数都没有,并在包装代码引发异常时填充信息。

下面的代码解释了上下文管理器的工作流程。记下内部属性以及属性值在不同场景中的变化方式。进入上下文管理器后,属性值设置为 true,退出时,其值设置为 false。一旦 with 语句中的代码块完成执行,就会调用 __exit__ 方法。

class ContextCheck(object):
      
    def __init__(self):
        self.inside = False
  
    def __enter__(self):
        self.inside = True
        return self
  
    def __exit__(self, exc_type, exc_instance, traceback):
        self.inside = False
  
  
cntCheck = ContextCheck()
print(cntCheck.inside)
  
with cntCheck:
    print(cntCheck.inside)
  
print(cntCheck.inside)

输出

False
True
False

异常处理

使用上下文管理器如何处理异常?
使用 __exit__ 方法,上下文管理器处理包装代码引发的异常。 __exit__ 方法有 3 个位置参数:

  • 异常类型
  • 异常的一个实例
  • 回溯选项

默认情况下,所有三个值都是无。当 __exit__ 方法收到异常时,该方法可以通过 2 种不同的方式处理异常:

  • 重新引发异常
  • 抑制异常

重新引发异常

__exit__ 方法可以通过返回 False 的 return 语句重新引发异常。让我们看看下面的代码:

class BubbleExc(object):
    def __enter__(self):
        return self
  
    def __exit__(self, ex_type, ex_instance, traceback):
        if ex_instance:    
            print('Exception: % s.' % ex_instance)
        return False
  
with BubbleExc():
    1 / 0

输出

Exception: division by zero.
Traceback (most recent call last):
  File "C:\Users\Sonu George\Documents\Python\Context Managers\bubbleExc.py", line 11, in 
    1/0
ZeroDivisionError: division by zero

抑制异常

__exit__ 方法可以通过返回 true 来抑制异常。请参阅下面的示例以了解详细信息。

class SuppressExc(object):
    def __enter__(self):
        return self
  
    def __exit__(self, ex_type, ex_instance, traceback):
        if ex_instance:
            print('Suppressing exception: % s.'% ex_instance)
        return True
  
with SuppressExc():
    1 / 0

输出>

Suppressing exception: division by zero.

何时应该编写上下文管理器

  • 释放资源:释放资源通常以样板代码告终。使用上下文管理器,程序员可以在 __enter__ 方法中打开资源并在 __exit__ 方法中关闭它,并且可以按需重用功能。
  • 避免重复:使用 except 子句进行异常处理会导致重复代码。使用上下文管理器,程序员可以引发或抑制异常,重要的是它被定义在一个地方,因此可以避免重复。

编写上下文管理器

将上下文管理器实现为类

使用上下文管理器写入文件的示例方法。这是完整的代码。

class FileClass(object):
      
    def __init__(self, f_name, func):
        self.f_obj = open(f_name, func)
          
    def __enter__(self):
        return self.f_obj
  
    def __exit__(self, type, value, traceback):
        self.f_obj.close()
  
with FileClass('sample.txt', 'w') as f_open:
    f_open.write('Congratulations, Good Work !')

输出

Congratulations, Good Work!

使用上下文管理器关闭页面

此处 page.close() 将在完成读取页面后调用(从 with 块退出时)。

from contextlib import closing
from urllib.request import urlopen
  
with closing(urlopen('https://www.geeksforgeeks.org/')) as page:
    for ln in page:
        print(ln)

输出

使用上下文管理器作为函数装饰器和生成器

使用Python contexlib模块,您可以将上下文管理器实现为装饰器,并且方法中的yield语句可以灵活地将其用作生成器。要理解这个程序,你应该有关于装饰器和生成器的先验知识。

from contextlib import contextmanager
  
@contextmanager
def open_file(name):
    try:
        print('File Open')
        f = open(name, 'r')
        yield f
    finally:
        print('Close Operation')
        f.close()
  
  
  
def main():
    with open_file('sample.txt') as f:
        data = f.read()
        print(data)
main()

这里@contextmanager包装了open_file方法。当函数open_file被调用时,上下文管理器的 __enter__ 方法执行并将控制权传递给open_file方法,在该方法中文件打开并产生对可调用的文件引用with open_file('sample.txt') as f并且在执行之前暂停执行最后阻塞。

一旦 with 语句中的代码被执行,它会将控制权交还给open_file并从停止的地方开始执行。在这种情况下, finally块执行并关闭文件。执行完成后,调用 __exit__ 方法并处理包装代码引发的任何异常。

通过检查异常类的实例来处理异常

在这种情况下,我们将检查异常是否是异常类的实例。下面我们将创建一个子类 'TypeErrorSubclass',它派生自 'TypeError' 异常类,并使用 raise 语句我们将引发 'TypeErrorSubclass' 异常。这是完整的代码。

class TypeErrorSubClass(TypeError):
    pass
  
  
class ExceptionClass(object):
    def __enter__(self):
        return self
  
    def __exit__(self, ex_type, ex_instance, traceback):
        # Return True if there is no exception
        if not ex_type:
            return True
  
        # Return True if execution type is
        # equal or a subclass of TypeError
        if issubclass(ex_type, TypeError):
            print('Handling TypeError: % s' % ex_instance)
            return True
  
        return False
  
with ExceptionClass():
    raise TypeErrorSubClass('Type Error')

输出

Handling ValueError: Type Error

__exit__ 方法使用issubclass(ex_type, TypeError)检查引发的异常是否是 'TypeError' 类的实例,如果是实例,则通过返回 True 来抑制异常。

概括

上下文管理器确保资源被正确释放,其结构方式提供了在不同位置重用异常处理代码的灵活性,从而避免代码重复。上下文管理器用于包装任意代码块。但是,可以将上下文管理器用作装饰器,这避免了为某些任务单独编写上下文管理器和装饰器的需要。