📜  Python中的上下文管理器

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

Python中的上下文管理器

管理资源:在任何编程语言中,文件操作或数据库连接等资源的使用都非常普遍。但这些资源供应有限。因此,主要问题在于确保在使用后释放这些资源。如果不释放它们,则会导致资源泄漏,并可能导致系统变慢或崩溃。如果用户有一种自动设置和拆除资源的机制,那将非常有帮助。在Python中,可以通过使用有助于正确处理资源的上下文管理器来实现。执行文件操作的最常见方法是使用with关键字,如下所示:

# Python program showing 
# a use of with keyword
  
with open("test.txt") as f:   
    data = f.read()

让我们以文件管理为例。打开文件时,会消耗一个文件描述符,这是一个有限的资源。一个进程一次只能打开一定数量的文件。下面的程序演示了它。

file_descriptors = []
for x in range(100000):
    file_descriptors.append(open('test.txt', 'w'))

输出:

Traceback (most recent call last):
  File "context.py", line 3, in 
OSError: [Errno 24] Too many open files: 'test.txt'

一条错误消息,指出打开的文件过多。上面的例子是一个文件描述符泄漏的例子。发生这种情况是因为打开的文件太多并且没有关闭。程序员可能会忘记关闭打开的文件。

使用上下文管理器管理资源:

假设一个代码块引发了一个异常,或者如果它有一个具有多个返回路径的复杂算法,那么在所有地方关闭一个文件变得很麻烦。
通常在其他语言中处理文件时try-except-finally用于确保文件资源在使用后关闭,即使出现异常也是如此。 Python提供了一种管理资源的简单方法:上下文管理器。使用with关键字。当它被评估时,它应该产生一个执行上下文管理的对象。上下文管理器可以使用类或函数(带有装饰器)来编写。

创建上下文管理器:

使用类创建上下文管理器时,用户需要确保该类具有方法: __enter__( )__exit__() 。 __enter__() 返回需要管理的资源,而 __exit__() 不返回任何内容,而是执行清理操作。
首先,让我们创建一个简单的类ContextManager来了解使用类创建上下文管理器的基本结构,如下所示:

# Python program creating a
# context manager
  
class ContextManager():
    def __init__(self):
        print('init method called')
          
    def __enter__(self):
        print('enter method called')
        return self
      
    def __exit__(self, exc_type, exc_value, exc_traceback):
        print('exit method called')
  
  
with ContextManager() as manager:
    print('with statement block')

输出:

init method called
enter method called
with statement block
exit method called

在这种情况下,会创建一个 ContextManager 对象。这是在as关键字 ie manager之后分配给变量的。在运行上述程序时,将依次执行以下命令:

  • __在里面__()
  • __进入__()
  • 语句体( with块内的代码)
  • __exit__()[此方法中的参数用于管理异常]

使用上下文管理器进行文件管理:

让我们应用上述概念来创建一个有助于文件资源管理的类。 FileManager 类有助于打开文件、写入/读取内容然后关闭它。

# Python program showing
# file management using 
# context manager
  
class FileManager():
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
        self.file = None
          
    def __enter__(self):
        self.file = open(self.filename, self.mode)
        return self.file
      
    def __exit__(self, exc_type, exc_value, exc_traceback):
        self.file.close()
  
# loading a file 
with FileManager('test.txt', 'w') as f:
    f.write('Test')
  
print(f.closed)

输出:

True


使用上下文管理器和 with 语句进行文件管理:

在执行with块时,依次发生以下操作:

  • 执行 __init__ 方法时,使用test.txt作为文件名和w (写入)作为模式创建FileManager对象。
  • __enter__ 方法以写入模式(设置操作)打开test.txt文件并将FileManager对象返回给变量f
  • 文本“测试”被写入文件中。
  • __exit__ 方法负责在退出with块(拆卸操作)时关闭文件。
    运行print(f.closed)时,输出为True ,因为FileManager已经负责关闭文件,否则需要显式完成。

使用上下文管理器进行数据库连接管理:

让我们创建一个简单的数据库连接管理系统。一次可以打开的数据库连接数也是有限的(就像文件描述符一样)。因此,上下文管理器有助于管理与数据库的连接,因为程序员可能会忘记关闭连接。

# Python program shows the
# connection management 
# for MongoDB
  
from pymongo import MongoClient
  
class MongoDBConnectionManager():
    def __init__(self, hostname, port):
        self.hostname = hostname
        self.port = port
        self.connection = None
  
    def __enter__(self):
        self.connection = MongoClient(self.hostname, self.port)
        return self
  
    def __exit__(self, exc_type, exc_value, exc_traceback):
        self.connection.close()
  
# connecting with a localhost
with MongoDBConnectionManager('localhost', '27017') as mongo:
    collection = mongo.connection.SampleDb.test
    data = collection.find({'_id': 1})
    print(data.get('name'))


使用上下文管理器和 with 语句进行数据库连接管理:

在执行with块时,依次发生以下操作:

  • 执行 __init__ 方法时,会创建一个MongoDBConnectionManager对象,其中localhost作为主机名, 27017作为端口。
  • __enter__ 方法打开 mongodb 连接并将MongoDBConnectionManager对象返回给变量mongo
  • 访问 SampleDb 数据库中的测试集合并检索_id =1 的文档。打印文档的名称字段。
  • __exit__ 方法负责在退出with块(拆卸操作)时关闭连接。