说明
《Python 教程》 持续更新中,提供建议、纠错、催更等加作者微信: gairuo123(备注:pandas教程)和关注公众号「盖若」ID: gairuo。跟作者学习,请进入 Python学习课程。欢迎关注作者出版的书籍:《深入浅出Pandas》 和 《Python之光》。
Python 2.5 就引入了上下文管理器(context manager),是一个便捷性的操作,它可以划定某个对象的使用范围。
使用 with as:
with open('gairuo.txt', 'w') as file:
file.write('hello world !')
如果不使用上下文管理器则代码是这样的:
try:
file = open('gairuo.txt', 'w')
file.write('hello world !')
except FileNotFoundError:
print("文件不存在!")
except Exception as e:
print(f"发生了其他错误: {e}")
finally:
if 'file' in locals():
file.close()
实现上下文管理器:
# 一个简单的文件写入对象
class FileWriter(object):
def __init__(self, file_name):
self.file_name = file_name
def __enter__(self):
self.file = open(self.file_name, 'w')
return self.file
def __exit__(self, exc_type, exc_value, traceback):
self.file.close()
# with 使用 FileWriter
with FileWriter('my_file.txt') as f:
f.write('hello world')
上下文管理器顾名思义是用于管理上下文(即环境)的,我们的操作代码在中间,中间的代码执行需要之前的一些资源引入和之后资源清理关闭等操作,就如冲锋和垫后。
为什么要引入上下文管理器呢?上常见的场景是:
模式为上文-操作-下文,因为以上都是基于 IO 等资源操作,往往这些资源都是有限的,如果由于忘记等原因没有及时关闭的话,可能会造成错误。操作系统同一时间能打开的文件数量也是有限的,如果同时打开多个资源处理后又不关闭会造成资源耗尽。
因此,上下文管理器是指在一段代码执行之前做一些预处理工作;执行之后做一些清理工作,比如上例的几种场景。如同我们看书时,看前打开书,看完合上书。
Python 引入 with as 语句能让我们用简单的代码实现上下文的管理,省去 try/finally 的繁琐操作,让代码简化。
上下文管理器 是一个对象,它定义了在执行 with 语句时要建立的运行时上下文。 上下文管理器处理进入和退出所需运行时上下文以执行代码块。 通常使用 with 语句(在 with 语句 中描述),但是也可以通过直接调用它们的方法来使用。
上下文管理器的典型用法包括保存和恢复各种全局状态,锁定和解锁资源,关闭打开的文件等等。
with 语句语法有以下几种形式:
with EXPRESSION as TARGET:
SUITE
# 以及
with A() as a, B() as b:
SUITE
# 和(3.10 版支持)
with (
A() as a,
B() as b,
):
SUITE
# 后两种相当于
with A() as a:
with B() as b:
SUITE
同时包含 __enter__()
和 __exit__()
方法的对象就是上下文管理器。
with 语句的执行过程如下:
__enter__()
以便后续使用。__exit__()
以便后续使用。__enter__()
方法。__enter__()
的返回值将被赋值给它。__enter__()
方法返回时未发生错误,则 __exit__()
将总是被调用。 因此,如果在对目标列表赋值期间发生错误,则会将其视为在语句体内部发生的错误。 参见下一步。__exit__()
方法。 如果语句体的退出是由异常导致的,则其类型、值和回溯信息将被作为参数传递给 __exit__()
。 否则的话,将提供三个 None 参数。__exit__()
方法的返回值为假,则该异常会被重新引发。 如果返回值为真,则该异常会被抑制,并会继续执行 with 语句之后的语句。__exit__()
的返回值会被忽略,并会在该类退出正常的发生位置继续执行。with as 相当于:
manager = (EXPRESSION)
enter = type(manager).__enter__
exit = type(manager).__exit__
value = enter(manager)
hit_except = False
try:
TARGET = value
SUITE
except:
hit_except = True
if not exit(manager, *sys.exc_info()):
raise
finally:
if not hit_except:
exit(manager, None, None, None)
要实现一个上下文管理器,需要让对象实现以下方法:
object.__enter__(self)
object.__exit__(self, exc_type, exc_value, traceback)
如果提供了异常,并且希望方法屏蔽此异常(即避免其被传播),则应当返回真值。 否则的话,异常将在退出此方法时按正常流程处理。
请注意 __exit__()
方法不应该重新引发被传入的异常,这是调用者的责任。
Python 内置一些内置对象支持了上下文协议:
最常用的是 file 对象,使用它的 open() 操作,见教程:open()。
我们想要使用 with 语句的便捷功能就要实现上下文协议中的特殊方法,那有没有办法快速将一个函数实现为上下文管理器吗?可以的,使用 Python 标准库提供的 contextlib 模块。
使用 contextlib 模块,我们可以把上下文管理器当成一个「装饰器」来使用。contextlib 模块提供了 @contextmanager 装饰器和 closing 方法。
装饰器通过 yield 将函数分割成两部分,yield 上面的语句在 __enter__
方法中执行,yield 下面的语句在 __exit__
方法中执行,紧跟在 yield 后面的参数是函数的返回值。
下面是一个抽象的示例,展示如何确保正确的资源管理:
from contextlib import contextmanager
@contextmanager
def managed_resource(*args, **kwds):
# Code to acquire resource, e.g.:
resource = acquire_resource(*args, **kwds)
try:
yield resource
finally:
# Code to release resource, e.g.:
release_resource(resource)
>>> with managed_resource(timeout=3600) as resource:
... # Resource is released at the end of this block,
... # even if code in the block raises an exception
被装饰的函数在被调用时,必须返回一个 generator 迭代器。 这个迭代器必须只 yield 一个值出来,这个值会被用在 with 语句中,绑定到 as 后面的变量,如果给定了的话。
当生成器发生 yield 时,嵌套在 with 语句中的语句体会被执行。 语句体执行完毕离开之后,该生成器将被恢复执行。 如果在该语句体中发生了未处理的异常,则该异常会在生成器发生 yield 时重新被引发。 因此,你可以使用 try...except...finally
语句来捕获该异常(如果有的话),或确保进行了一些清理。 如果仅出于记录日志或执行某些操作(而非完全抑制异常)的目的捕获了异常,生成器必须重新引发该异常。 否则生成器的上下文管理器将向 with 语句指示该异常已经被处理,程序将立即在 with 语句之后恢复并继续执行。
contextmanager() 使用 ContextDecorator 因此它创建的上下文管理器不仅可以用在 with 语句中,还可以用作一个装饰器。当它用作一个装饰器时,每一次函数调用时都会隐式创建一个新的生成器实例(这使得 contextmanager() 创建的上下文管理器满足了支持多次调用以用作装饰器的需求,而非“一次性”的上下文管理器)。
以下是一个文件处理函数的实现:
# 导入装饰器
from contextlib import contextmanager
# 装饰器装饰函数,让其称为一个上下文管理器对象
@contextmanager
def my_open(path, mode):
try:
# 打开文件
file = open(file_name, file_mode)
# yield之前的代码好比是上文方法
yield file
except Exception as e:
print(e)
finally:
print("over")
# yield下面的代码好比是下文方法
file.close()
# 使用with语句
with my_open('gairuo.txt', 'w') as f:
f.write('hello world !')
contextlib 还实现了异步上下文管理器 @asynccontextmanager
。
异步上下文管理器 是 上下文管理器 的一种,它能够在其 __aenter__
和 __aexit__
方法中暂停执行。
异步上下文管理器可在 async with 语句中使用。
object.__aenter__(self)
: 在语义上类似于 __enter__()
,仅有的区别是它必须返回一个 可等待对象。object.__aexit__(self, exc_type, exc_value, traceback)
: 在语义上类似于 __exit__()
,仅有的区别是它必须返回一个 可等待对象。异步上下文管理器类的一个示例:
class AsyncContextManager:
async def __aenter__(self):
await log('entering context')
async def __aexit__(self, exc_type, exc, tb):
await log('exiting context')
它是 3.5 新版功能。
更新时间:2024-05-28 14:23:56 标签:python with 上下文