Python上下文管理器
今天在地铁上又看了一遍Python的上下文管理器,自己好像也没有完整记录过,所以刚到单位,就想写一篇文章。
本文主要讲解:
- Python上下文管理的定义与作用;
- 上下文管理器的使用;
- 自定义上下文管理器;
- 上下文管理器装饰器
一、Python上下文管理的定义与作用
上下文管理器是Python2.5之后才出现的概念。上下文管理器规定了某个对象的使用范围,当进入或者离开了使用范围,都会有相应的一些调用,比如代码块开始时执行一些准备,代码块结束时结束一些操作。它更多的是用于资源的分配和释放上,即在开始时分配资源,结束时释放一些资源。比如在执行数据库查询时要建立连接,查询结束后要释放连接;写文件时要先打开文件,写结束后,要关闭文件等等。还有,就是资源的加锁和解锁,比如在使用多线程时,可能会用到加锁和解锁。
上下文管理器可以通过使用更可读、更精简的代码实现资源的分配与释放。
二、上下文管理器的使用
上下文管理器的一个常见例子,就是建立或关闭数据库连接。使用装饰器实现:
from baseclass.database import Database
def dbdecorator(func):
def wrapper(*args,**kwargs):
db = Database()
db.connect()
ret = func(db,*args,**kwargs)
db.close()
return ret
return wrapper()
@dbdecorator
def query(db,*args,**kwargs):
return db.query(*args,**kwargs)
对于上下文管理器的使用,最常见的是使用with语句,with语句可构建资源的分配与释放的语法糖。先拿最常见的例子来说,即文件的打开与关闭。
正常语法:
def load_data(filename):
f = file(filename,'w')
try:
f.write('test file')
finally:
f.close()
即正常情况下,你要显示的打开和关闭文件。但如果你用with语句,就会更可读,且永远不会因为忘记关闭文件而担忧:
def load_data(filename):
with file(filename,'w') as f:
f.write('test file')
三、自定义上下文管理器
要实现一个自定义的上下文管理器,肯定要实现两个方法,一是进入对象范围时的准备工作,二是离开对象范围时的结束工作。
Python提供了两个类的方法分别实现上述功能:
- __enter__ 进入对象范围时(一般代码块开始)被调用;
- __exit__ 离开对象范围时(代码块结束)呗调用;
因此,一个Python类,只要实现了上述两种方法,就可以说是一个上下文管理器。
class FileRead(object):
def __init__(self,filename,read_method='w'):
self.obj = file(filename,read_method)
def __enter__():
return self.obj
def __exit__():
self.obj.close()
with FileRead('test.txt') as f:
f.write('s')
再拿一个例子看看,Flask源码:
class IdentityContext(object):
"""The context of an identity for a permission.
The principal behaves as either a context manager or a decorator. The
permission is checked for provision in the identity, and if available the
flow is continued (context manager) or the function is executed (decorator)
"""
def __init__(self, permission, http_exception=None):
self.permission = permission
self.http_exception = http_exception
@property
def identity(self):
"""The identity of this principal
"""
return g.identity
def can(self):
"""Whether the identity has access to the permission
"""
return self.identity.can(self.permission)
def __call__(self, f):
@wraps(f)
def _decorated(*args, **kw):
with self:
rv = f(*args, **kw)
return rv
return _decorated
def __enter__(self):
# check the permission here
if not self.can():
if self.http_exception:
abort(self.http_exception, self.permission)
raise PermissionDenied(self.permission)
def __exit__(self, *args):
return False
上面的IdentityContext是一个类装饰器,实现了上下文管理器的功能。该类定义了__enter__和__exit__两个方法。
class Permission(object):
def require(self, http_exception=None):
"""Create a principal for this permission.
return IdentityContext(self, http_exception)
上面就实现了上下文管理器的功能,那为什么这里没使用with语句,就可以呢?
如果细心的话,可能会看到IdentityContext的__call__方法,它里面使用了with语法。
with self:语句开始就自动执行__enter__ 方法,with语句结束,就执行__exit__ 方法。
那这个__call__方法的作用是啥?
对象通过提供__call__方法可以模拟函数的行为,如果一个对象提供了该方法,就可以像函数一样使用它,也就是说x(arg1, arg2...) 等同于调用x.__call__(self, arg1, arg2) 。比如:
class Test(object):
def __call__(a):
print a
t = Test()
t('e')
>> e
说完了__call__,再看内部语句,实际上是一个函数装饰器,并且调用自己。
对于异常的处理,如果发现异常,就会调用__exit__方法,如果我们想忽略异常,就直接让其返回True即可。
def __exit__(self,exc_type, exc_val, exc_t):
#三个参数exc_type, exc_val, exc_tb分别代表异常类型,异常值,和异常的Traceback
return True
四、上下文管理器装饰器
除了上面要写__enter__和__exit方法,我们可以用标准库中的装饰器改写,即contextlib.contextmanager。
@contextmanager
def test_context():
start = time.time()
try:
yield
finally:
end = time.time()
print 'it cost {0}'.format(end-start)
with test_context():
time.sleep(10)
>>it cost 10.0009999275
test_context函数中yield之前的代码类似__enter__方法;yield之后的代码类似于__exit__方法。
微信分享/微信扫码阅读