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_guard<std::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
语句来保证代码执行的安全的同时维持代码的简洁与优雅。
相关文章
- 如何为pycharm配置Python解释器_pycharm选择python解释器
- python中矩阵的转置怎么写_Python 矩阵转置的几种方法小结
- python字符串转化列表_Python列表到字符串的转换[通俗易懂]
- 8 月编程语言排行榜:没有一门语言能比得上 Python
- python常用面试题_Python+Selenium 常见面试题整理[通俗易懂]
- python格式化转换_Python进制转换format格式化[通俗易懂]
- Python-opencv读取深度图像
- 手机python 3.8解释器_Python 3.8 解释器安装教程
- gyp ERR! stack Error: Can't find Python executable 'python'
- 【说站】python数据离散化是什么
- 【说站】python如何创建操作页面
- 【说站】python美元转换成人民币转换代码
- 【说站】python用户如何自定义异常
- 遗传算法的应用实例python实现_遗传算法Python解决一个问题
- python输出unicode编码_Python以utf8编码读取文件
- python的特点和优势_Java与Python异同
- python图像多层小波分解_Python中图像小波分解与重构以及灰度图加噪
- 用python生成随机数的几种方法「建议收藏」
- python如何生成随机数_Python生成50个随机数
- Python 运算符与流程控制
- 下列python语句的输出结果是print_下列 Python语句的输出结果是「建议收藏」
- 【7】python_matplotlib 输出(保存)矢量图方法;画图时图例说明(legend)放到图像外侧;Python_matplotlib图例放在外侧保存时显示不完整问题解决
- python – python – json
- python-Python与MongoDB数据库-使用Python执行MongoDB查询(一)
- python-Django-基础概念(一)
- Python set集合基本操作(添加、删除、交集、并集、差集)
- 扒糗事百科精华的python爬虫详解编程语言
- Linux查看Python版本的有效方法(linux查看python版本)
- Python在连接MSSQL数据库中的应用(python连mssql)
- ymysql在Python中使用Redis CLI和Pymysql(redis cli p)
- Linux 下 Python 升级:轻松完成升级操作(linux下升级python)