Python上下文管理详解

发布时间:2023年12月20日

? ? ? ?


引言

在 Python 编程中,有效地管理资源和处理异常是至关重要的。上下文管理器作为一种强大的工具,提供了一种优雅的方式来管理资源,确保它们在使用完毕后能够被正确释放。通过结合?with?语句,上下文管理器使得资源的获取和释放变得简单而可靠,同时也使得异常处理变得更加优雅和简洁。本文将深入探讨 Python 中的上下文管理器,介绍其概念、用法和实际应用,并提供丰富的代码示例,帮助读者更好地理解和运用这一强大的特性。

当谈论 Python 中的上下文管理时,我们通常是指?with?语句和上下文管理器。上下文管理器可以让我们更方便地管理资源,比如文件、网络连接或者数据库连接,同时也可以确保资源在使用完毕后得到正确的清理和释放。在本文中,我将详细介绍上下文管理器的概念、用法和实例,并提供丰富的代码示例。


1. 什么是上下文管理器?

在 Python 中,上下文管理器是指实现了?__enter__?和?__exit__?方法的对象。当我们使用?with?语句时,会调用上下文管理器的?__enter__?方法获取资源,然后在?with?代码块执行结束后,无论是正常结束还是出现异常,都会调用?__exit__?方法来进行清理和释放资源。

上下文管理器可以用于许多场景,比如文件操作、线程锁、数据库连接等,它们能够确保资源的正确管理和释放,避免出现资源泄漏等问题。

一个上下文管理器的类,最起码要定义?__enter__?和?__exit__?方法。 让我们来构造我们自己的开启文件的上下文管理器,并学习下基础知识。

class?File(object):
????def?__init__(self,?file_name,?method):
????????self.file_obj?=?open(file_name,?method)
????def?__enter__(self):
????????return?self.file_obj
????def?__exit__(self,?type,?value,?traceback):
????????self.file_obj.close()

通过定义?__enter__?和?__exit__?方法,我们可以在with语句里使用它。我们来试试:

with?File('demo.txt',?'w')?as?opened_file:
????opened_file.write('Hola!')

我们的?__exit__?函数接受三个参数。这些参数对于每个上下文管理器类中的?__exit__?方法都是必须的。我们来谈谈在底层都发生了什么。

  1. with?语句先暂存了?File?类的?__exit__?方法。

  2. 然后它调用?File?类的?__enter__?方法。

  3. __enter__?方法打开文件并返回给?with?语句。

  4. 打开的文件句柄被传递给?opened_file?参数。

  5. 我们使用?.write()?来写文件。

  6. with?语句调用之前暂存的?__exit__?方法。

  7. __exit__?方法关闭了文件。

2. 实现上下文管理器

我们也可以自定义上下文管理器,只需实现?__enter__?和?__exit__?方法即可。

2.1 基础实现

python复制代码class?MyContextManager:
????def?__enter__(self):
????????print('Entering?the?context')
????????#?返回需要被管理的资源
????????return?self

????def?__exit__(self,?exc_type,?exc_value,?traceback):
????????print('Exiting?the?context')
????????#?在退出上下文时进行清理工作

#?使用自定义的上下文管理器
with?MyContextManager()?as?manager:
????#?在这个代码块中使用?manager?管理的资源
????pass

2.2 嵌套使用

上下文管理器可以进行嵌套使用,这样可以方便地管理多个资源。上下文管理器的嵌套使用可以帮助我们方便地管理多个资源。这种嵌套使用可以确保资源的正确获取和释放,使代码更加清晰和易于维护。这里有一个示例,演示了如何嵌套使用多个上下文管理器:

class?DatabaseConnection:
????def?__enter__(self):
????????print('Opening?database?connection')
????????#?假设这里是连接数据库的代码
????????return?self

????def?__exit__(self,?exc_type,?exc_value,?traceback):
????????print('Closing?database?connection')
????????#?假设这里是关闭数据库连接的代码

class?FileOperation:
????def?__enter__(self):
????????print('Opening?file')
????????#?假设这里是打开文件的代码
????????return?self

????def?__exit__(self,?exc_type,?exc_value,?traceback):
????????print('Closing?file')
????????#?假设这里是关闭文件的代码

#?嵌套使用上下文管理器
with?DatabaseConnection()?as?db_connection:
????with?FileOperation()?as?file:
????????#?执行需要同时使用数据库连接和文件的操作
????????pass


在这个示例中,我们嵌套使用了 `DatabaseConnection` 和 `FileOperation` 两个上下文管理器,这样可以确保在操作完成后,数据库连接和文件都能被正确地关闭。

嵌套使用上下文管理器使得我们能够更加灵活地管理多个资源,确保资源的获取和释放都能得到正确处理。这种方式使得代码的可读性更强,同时也降低了出错的可能性。

希望这个示例能够帮助您更好地理解上下文管理器的嵌套使用。

3. 上下文管理器的应用


?3.1 文件操作

使用 with 语句管理文件资源

with?open('example.txt',?'r')?as?f:
????for?line?in?f:
????????print(line)
#?文件在?with?代码块结束后自动关闭

3.2 线程锁

import?threading

lock?=?threading.Lock()
with?lock:
????#?执行需要进行线程同步的操作
????pass
#?线程锁在?with?代码块结束后自动释放

3.3 数据库连接

import?pymysql

class?DBConnection:
????def?__enter__(self):
????????self.conn?=?pymysql.connect(host='localhost',?user='user',?password='password',?db='test_db')
????????self.cursor?=?self.conn.cursor()
????????return?self.cursor

????def?__exit__(self,?exc_type,?exc_value,?traceback):
????????self.cursor.close()
????????self.conn.close()

with?DBConnection()?as?cursor:
????cursor.execute('SELECT?*?FROM?example_table')
????#?执行数据库操作
#?数据库连接在?with?代码块结束后自动关闭

3.4 异常处理:

我们还没有谈到?__exit__?方法的这三个参数:typevalue?和?traceback。 在第4步和第6步之间,如果发生异常,Python 会将异常的?typevalue?和?traceback?传递给?__exit__?方法。 它让?__exit__?方法来决定如何关闭文件以及是否需要其他步骤。在我们的案例中,我们并没有注意它们。

那如果我们的文件对象抛出一个异常呢?万一我们尝试访问文件对象的一个不支持的方法。举个例子:

with?File('demo.txt',?'w')?as?opened_file:
????opened_file.undefined_function('Hola!')

我们来列一下,当异常发生时,with?语句会采取哪些步骤。

  1. 它把异常的?typevalue?和?traceback?传递给?__exit__方法。

  2. 它让?__exit__?方法来处理异常。

  3. 如果?__exit__?返回的是 True,那么这个异常就被优雅地处理了。

  4. 如果?__exit__?返回的是 True 以外的任何东西,那么这个异常将被?with?语句抛出。
    在我们的案例中,__exit__?方法返回的是?None?(如果没有?return?语句那么方法会返回?None)。因此,with?语句抛出了那个异常。

Traceback?(most?recent?call?last):
File?"<stdin>",?line?2,?in?<module>
AttributeError:?'file'?object?has?no?attribute?'undefined_function'

我们尝试下在?__exit__?方法中处理异常:

class?File(object):
????def?__init__(self,?file_name,?method):
????????self.file_obj?=?open(file_name,?method)
????def?__enter__(self):
????????return?self.file_obj
????def?__exit__(self,?type,?value,?traceback):
????????print("Exception?has?been?handled")
????????self.file_obj.close()
????????return?True

with?File('demo.txt',?'w')?as?opened_file:
????opened_file.undefined_function()

#?Output:?Exception?has?been?handled


我们的 `__exit__` 方法返回了 `True`,因此没有异常会被 `with` 语句抛出。

这还不是实现上下文管理器的唯一方式。还有一种方式,我们会在下一节中一起看看。

上下文管理器在异常处理方面也非常有用,当?with?代码块中出现异常时,上下文管理器的?__exit__?方法会被调用,这样我们可以在?__exit__?方法中处理异常并进行资源的释放和清理。

class?MyContextManager:
????def?__enter__(self):
????????print('Entering?the?context')
????????return?self

????def?__exit__(self,?exc_type,?exc_value,?traceback):
????????print('Exiting?the?context')
????????if?exc_type?is?not?None:
????????????print(f'An?error?occurred:?{exc_value}')
????????#?在退出上下文时进行清理工作

#?使用自定义的上下文管理器处理异常
with?MyContextManager()?as?manager:
????#?在这个代码块中可能会出现异常
????raise?ValueError('Something?went?wrong')

总结

上下文管理器作为 Python 中极为重要的概念之一,为资源管理和异常处理提供了一种优雅而可靠的解决方案。通过定义自己的上下文管理器,我们可以轻松地扩展其应用范围,实现更多自定义的资源管理和清理逻辑。同时,上下文管理器的嵌套使用可以帮助我们更好地处理多个资源的管理,使得代码的结构更加清晰和可维护。通过本文的学习,读者可以更深入地理解上下文管理器的原理和用法,为编写更加健壮和可靠的 Python 代码打下坚实的基础。希望读者能够充分利用上下文管理器这一强大工具,提高自己的编程效率和代码质量。

文章来源:https://blog.csdn.net/Rocky006/article/details/135010525
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。