RAII概念与在Python中的应用
RAII 概念与在 Python 中的应用
RAII(Resource Acquisition Is Initialization),即资源获取即初始化,是一种设计模式,用于解决资源的获取与初始化的问题,最早在 C++中提出与推广。 在这篇文章我来简单地介绍一下 RAII 的概念,以及在 Python 中的应用。
RAII 的概念
在计算机与程序的世界中,有一些资源,比如文件、网络连接、数据库连接、线程、进程等,这些资源在使用的时候需要获取,在使用完成后需要释放。如果不及时释放,会导致资源泄露,造成资源的浪费,程序出错甚至系统崩溃。
一个简单的示例就是文件的读写。
f = open('test.json', 'r')
raw = f.read()
data = json.loads(raw)
f.close()这段代码看起来没有什么问题,但是当test.json文件的内容不是合法的 JSON 格式时,第四行代码反序列化数据就会抛出异常,导致第五行代码无法执行,文件没有被关闭。
这个例子告诉我们在处理一些资源时,需要注意在操作过程中是否会发生一些意外情况,例如抛出异常,并且在意外情况发生后,也需要关闭资源。
在 Python2.5 之前的版本中,我们用try-finally来保证程序最终会关闭资源。
try:
f = open('test.json', 'r')
raw = f.read()
data = json.loads(raw)
except JSONDecodeError:
...
finally:
f.close()在简单的文件读取操作中,使用try语句多少有点大材小用。为了更好地处理类似的资源管理问题,Python2.5 引入了with语句,做到无论语句块中的代码执行是否抛出异常,都可以在退出with语句块时执行清零代码。
事实上在 Python 中进行文件读写的标准方式就是使用with open语句。
with open('test.json', 'r') as f:
raw = f.read()
data = json.loads(raw)Python 中的with语句就是 RAII(Resource Acquisition Is Initialization)的一种具体实现。
RAII 模式的核心就是让资源和资源对应的对象的生命周期保持一致:
- 对象的初始化会导致资源的初始化,
- 对象的释放会导致资源的释放。
实际上最理想的方式是在文件对象被清理的时候自动关闭文件,然而像 Python、Java 这些有自动管理内存的垃圾回收机制的语言中,一般不会手动控制对象的回收,也就无法保证文件关闭的时机符合预期。一般带 GC 的语言会有自己的 RAII 模式的实现机制,例如 Python 中的with语句和 Java 中的try with语句。
RAII 在无 GC 的语言(C++,Rust)中其实表现的更自然。
std::mutex m,
{
std::lock_guardstd::mutex>
lockGuard(m);
sharedVariable= getVar();
}
在上述的 C++代码中,lockGuard对象在初始化时就会获取m锁,并且在lockGuard对象被释放时,会自动释放m锁,保证了sharedVariable的值不会被其他线程访问。同时也规避了传统的m.lock()和m.unlock()的写法。
当然本文的主题是 Python, 接下来我们将了解一下with语句的更多细节。
with语句
Python 中with语句的语法如下:
with expression [as variable]:
with-block其中experssion表达式执行后得到的是一个上下文管理器对象(Context Manager)。
一个上下文管理器可以是任何对象,只要它实现了__enter__和__exit__方法。
__enter__方法的返回值会赋值给variable变量(需要使用as语句为其绑定一个名字)。with-block语句块会在expression执行完后执行。__exit__方法会在with-block语句块执行完后执行(即使 with-block 抛出了异常)。
一个简单的上下文管理器对象的实现如下:
class ContextManager:
def __enter__(self):
print('enter')
return self
def __exit__(self, ex_type, ex_value, ex_traceback):
print('exit')
if ex_value:
raise ex_value值得注意的是,__exit__方法的三个参数分别是异常类型、异常值和异常的追踪信息。当然如果没有抛出异常,那么这三个参数都是None。
我们可以通过with语句来使用ContextManager对象:
在with-block抛出异常时,__exit__方法也会被调用。
在这个例子中,with-block抛出的异常会被__exit__方法捕获,并且被__exit__方法抛出。
如果不重新抛出异常的话,就会丢失异常信息,类似于在try/except语句中捕获Exception却不做任何处理,是不负责任的行为。
应该区分哪些异常是可以处理的,无法处理的异常应该再抛出,由调用者来处理。
使用contextlib定义上下文管理器
除了给类定义__enter__方法和__exit__方法,Python 官方还提供了contextlib标准库用于简化上下文管理器的定义。
使用contextlib.contextmanager装饰器装饰生成器函数,yield语句之前的代码相当于传统上下文管理器的__enter__方法,yield的值会被赋值给as后的变量,yield之后的代码相当于__exit__方法,会在退出with-block后执行。
from contextlib import contextmanager
@contextmanager
def myopen(path:str,mode:str):
f = open(path,mode)
try:
yield f
finally:
f.close()
with myopen('test.json','r') as f:
raw = f.read()
data = json.loads(raw)上述代码中我们使用contextlib, 定义了一个myopen函数来模拟 Python 内置的open函数,在退出with-block后执行f.close()方法,保证了文件被正确释放。
常见的上下文管理器
Python 除了内置的with open处理文件之外,还有很多的流行的第三方库也广泛使用了with语句和上下文管理器进行资源管理。
例如requests库中可以使用with语句来管理Session对象,退出with语句后 session 会自动关闭。
import requests
with requests.Session() as s:
s.get('https://httpbin.org/cookies/set/key/value')
resp = s.get('https://httpbin.org/cookies')
print(resp.json()) # {
'cookies': {
'key': 'value'}
}
redis库提供的lock方法也是使用with语句来管理锁,退出with语句后锁会自动释放。
import redis
client = redis.Redis()
with client.lock('LOCK_KEY'):
print('do_something')总结
RAII是一个比较先进的理念, with语句是其在 Python 中的实现。在面向资源管理相关的业务场景时,可以更多地使用with语句来保证代码执行的安全的同时维持代码的简洁与优雅。
声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!
若转载请注明出处: RAII概念与在Python中的应用
本文地址: https://pptw.com/jishu/6108.html
