0.概述
上下文管理器是定义在语句中使用with
语法
执行的运行时上下文的对象。
1.概念
从一个简单的示例开始,来理解上下文管理器的概念。
假设有一个名为data.txt
包含整数100的文件。
以下程序读取data.txt
文件,将其内容转换为数字,并将结果显示为标准输出:
f = open('data.txt')
data = f.readlines()
# convert the number to integer and display it
print(int(data[0]))
f.close()
代码简单明了。但data.txt
可能包含无法转换为数字的数据。在这种情况下,代码将导致异常。
例如,如果data.txt
包含字符串'100'
而不是数字 100,则会收到以下错误:
ValueError: invalid literal for int() with base 10: "'100'"
由于此异常,Python 可能无法正确关闭文件。
要解决此问题,可以使用try...except...finally声明:
try:
f = open('data.txt')
data = f.readlines()
# convert the number to integer and display it
print(int(data[0]))
except ValueError as error:
print(error)
finally:
f.close()
由于finally
块中的代码始终执行,因此代码将始终正确关闭文件。
此解决方案按预期工作。但是,它过于冗长。
因此,Python 提供了一种更好的方法,允许在完成处理文件后自动关闭文件。
这就是上下文管理器发挥作用的地方。
下面显示了如何使用上下文管理器来处理data.txt
文件:
with open('data.txt') as f:
data = f.readlines()
print(int(data[0])
在此示例中,我们将open()
函数与with
语句一起使用。执行块后,Python 将自动关闭。
2.with
语句
以下是with
语句的典型语法:
with context as ctx:
# use the the object
# context is cleaned up
它的工作流程如下:
- 当 Python 遇到该
with
语句时,它会创建一个新上下文。上下文可选择返回一个object- 在with块之后,Python 会自动清理上下文。
- ctx的作用域与with语句的作用域相同。这意味着可以在with内部和之后访问ctx
下面显示了如何访问语句后面的变量:
with open('data.txt') as f:
data = f.readlines()
print(int(data[0]))
print(f.closed) # True
3.上下文管理器协议
Python 上下文管理器基于上下文管理器协议工作。
上下文管理器协议具有以下方法:
__enter__()
– 设置上下文并选择性地返回一些对象__exit__()
– 清理对象。
如果希望一个class支持上下文管理器协议,则需要实现这两种方法。
假设ContextManager
是一个支持上下文管理器协议的类。
下面演示如何使用该ContextManager
类:
with ContextManager() as ctx:
# do something
# done with the context
将 ContextManager
类与with语句一起使用时,Python 会隐式创建 ContextManager
类的一个实例(临时对象),并在该实例上自动调用__enter__()方法。
该__enter__()方法可以选择返回一个对象。如果是这样,Python 会为返回的对象分配 ctx
需要注意,ctx引用了__enter__()方法返回的对象。它并不引用ContextManager
类的实例。
如果在 with 块内或块之后发生异常,Python 将在实例对象上调用__exit__()方法。
从功能上讲,该语句等效于以 下 try...finally 语句:
instance = ContextManager()
ctx = instance.__enter__()
try:
# do something with the txt
finally:
# done with the context
instance.__exit__()
__enter__() 方法
在__enter__()
方法中,可以执行必要的步骤来设置上下文。
(可选地是),可以从__enter__()
方法返回一个对象。
__exit__() 方法
Python 始终执行__exit__()
方法,即使块中发生异常也是如此。
该__exit__()
方法接受三个参数:异常类型、异常值和回溯对象。如果没有异常发生,所有这些参数都将是None
。
def __exit__(self, ex_type, ex_value, ex_traceback):
...
该__exit__()
方法返回一个布尔值,即 True或 False
如果返回值为 True,则 Python 将使任何异常保持静默。否则,它不会报出异常。
4.上下文管理器应用程序
从前面的示例中可以看出,上下文管理器的常见用法是自动打开和关闭文件。
但是,也可以在许多其他情况下使用上下文管理器:
1)Open – Close
如果要自动打开和关闭资源,可以使用上下文管理器。
例如,可以使用上下文管理器打开套接字并关闭它。
2) Lock – release
上下文管理器可以帮助更有效地管理对象的锁,它们允许获取锁并自动释放它。
3) Start – stop
上下文管理器还可以帮助用户处理需要启动和停止阶段的方案。
例如,可以使用上下文管理器来启动计时器并自动停止计时器。
3) Change – reset
上下文管理器可以使用更改和重置方案。
例如,应用程序需要连接到多个数据源。它有一个默认连接。
要连接到另一个数据源,请执行以下操作:
- 首先,使用上下文管理器将默认连接更改为新连接。
- 其次,使用新连接工作
- 第三,在完成使用新连接后将其重置回默认连接。
5.实现 Python 上下文管理器协议
下面显示了使用上下文管理器协议的open()函数的
简单实现:
class File:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
def __enter__(self):
print(f'Opening the file {self.filename}.')
self.__file = open(self.filename, self.mode)
return self.__file
def __exit__(self, exc_type, exc_value, exc_traceback):
print(f'Closing the file {self.filename}.')
if not self.__file.closed:
self.__file.close()
return False
with File('data.txt', 'r') as f:
print(int(next(f)))
它是如何工作的。
- 首先,使用
__init__()方法
初始化filename
与mode
- 其次,在
__enter__()
方法中打开文件并返回文件对象。- 第三,如果文件在
__exit__()
方法中打开,则关闭该文件。
6.使用上下文管理器实现启动和停止模式
下面定义了一个支持上下文管理器协议的Timer
类:
from time import perf_counter
class Timer:
def __init__(self):
self.elapsed = 0
def __enter__(self):
self.start = perf_counter()
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
self.stop = perf_counter()
self.elapsed = self.stop - self.start
return False
它是如何工作的。
- 首先,从time模块中导入
perf_counter
- 其次,在timer计时器中启动
__enter__()方法
- 第三,使用
_exit__()
方法停止计时器并返回消耗的时间。
现在,可以使用Timer
类来测量计算 1000 斐波那契所需的时间,即执行 100 万次的耗时:
def fibonacci(n):
f1 = 1
f2 = 1
for i in range(n-1):
f1, f2 = f2, f1 + f2
return f1
with Timer() as timer:
for _ in range(1, 1000000):
fibonacci(1000)
print(timer.elapsed)
7.小结
- 在语句中执行
with
时,使用 Python 上下文管理器定义运行时上下文。 应用__enter__()与__exit__()
方法以实现 上下文管理器协议。