Python 技巧探究:上下文管理器和with语句
一:前言
Python 里面的 with 语句是被认为是晦涩难懂的特征之一,但是当你窥视它的内部你就会发现这里面并没有什么魔法。事实上它可以帮助我们写一些整洁和可读性高的代码。
那么 with 语句适合用于哪方面呢?它可以帮助我们简化一些常见的资源管理模式,允许它们被提取和重用。
二:案例探究
还是来看一个最常见的打开文件的例子吧!
with open('name.txt','w') as f:
f.write('hello world')
使用 with 语句去打开文件的好处在于,不管文件是否正常打开,最后文件都会被正常的关闭。实际上前面的代码可以翻译为下面的方式:
f = open('name.txt','w')
try:
f.write('hello world')
finally:
f.clouse()
当然这样写看起来确实挺废话的,但是其实 try...finally 语法是很重要的。
如果不用这个语法,可能会这样写:
f = open('name.txt','w')
f.write('hello world')
f.clouse()
这样的写法看起来挺正常的,但是实际上它保证不了文件的正常关闭,如果文件没有正常关闭就会出现很多问题,所以使用 with 语句是很有必要的,它帮助我们正确的获取和释放资源。
with 语句的另一个好用的例子是在 Python 的标准库里的 threading.Lock
lock = threading.Lock
# 普通的方式:
lock.acquir()
try:
# do something
finally:
lock.release()
# 更好的方式:
with lock:
# do something
通过这两个例子可以看到:使用 with 语句允许我们抽象更多的资源处理逻辑,取代每次显式的使用 try...finally 语句,方便我们写代码。舒服了哦!
with 语句可以使得处理系统资源的代码可读性更高,它帮助我们避免 bug 或者忘记释放资源。
三:让自己的项目支持with语句
现在其实觉得 open() 和 threading.Lock 没啥特殊或者魔法的。他们实际上都是可以使用 with 语句的。 我们也可以在类里面通过实现上下门管理器提供一些功能。
啥是上下文管理器( context manager) ?它是一个简单的接口,最基本的是我们写的这个类要实现 双下划线方法:__enter__
和 __exit__
,Python 会在资源管理中适当的调用这两个方法。
让我们看看它是怎么实现 open() 上下文管理的:
class ManagedFile:
def __init__(self, name)
self.name = name
def __enter__(self):
self.file = open(self.name, 'w')
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
这个 ManagedFile
类就实现了上下文管理器协议,那么就支持使用 with 语句,就像使用最开始的 open() 的例子那样:
with ManagedFile('name.txt') as f:
f.write('hello Python')
当使用 with 语句进入上下文管理器时 Python 调用 __enter__
方法,同时将获取到资源,当离开上下文管理器的时候 Python 调用 __exit__
方法去释放资源。
像上面这样写一个基于上下文管理器的累不是在Python中支持 with 语句的唯一方法。 Python 标准库中的 contextlib
模块提供了一些基于上下文管理器的协议。
例如我们可以使用 contextlib.contextmanager
装饰一个基于生成器的工厂函数,这样就可以使用 with 语句,来看看例子吧:
from contextlib import contextmanager
@contextmanager
def manager_file(name):
try:
f = open(self.name, 'w')
yield f
finally:
f.close()
这样使用:
with manager_file('name.txt') as f:
f.write('hello python')
在这个例子中 manager_file()
是一个生成器,在开始的时候获取到资源,然后暂时性的停止程序 yiled
这个资源给调用者。当调用者离开了上下文,这个生成器就会继续执行后续的代码,所以最后资源可以被释放给系统。
上面一个基于类的和基于生成器的方法去使用 with 语句是等价的,选一个可读性好的就行。
这里有一个问题就是,基于装饰器的实现方法要求开发者懂一些例如装饰器、生成器这样高级的 Python 语法概念。不过真的想学习 Python 这些高级语法也是要学习的。