浅谈Python的上下文管理器

Admin 2018-09-25 11:52:08 Python

我们经常在Python代码中看到with语句,仔细分析下,会发现这个with语句功能好强大,可以自动关闭资源。这个在Python中叫上下文管理器(Context Manager)那我们什么时候用到它呢?

上下文管理器的作用

在很多情况下,当我们使用完一个资源后,我们需要手动的关闭它,如操作文件或数据库。但是,在使用资源的过程中,如果遇到异常,很可能错误被直接抛出,导致来不及关闭资源。所以在大部分程序语言里,我们使用“try-finally”语句来确保资源关闭。如

try:    
  f = open('test.txt', 'a+')    
  f.write('Foo\n')
finally:    
  f.close()

这样固然没有问题,但是当“try-finally”中间的逻辑复杂,而且还带有各种嵌套的话,代码就不容易维护。Python的with语句,可以说功能上是和“try-finally”几乎一样的,但代码简洁的多,如

with open('test.txt', 'a+') as f:    
   f.write('Foo\n')

with语句后面跟着open()方法,如果它有返回值的话,可以使用as语句将其赋值给f。在with语句块退出时,“f.close()”方法会被自动调用,即使“f.write()”出现异常,也能确保close()方法被调用。

自定义类来实现上下文管理器

上例中的open()方法是Python自带的,那我们怎么自己定义类型来使用with语句哪?其实只要定义的类实现“__enter__()”和“__exit__()”方法就可以使用。“__enter__()”方法会在with语句进入时被调用,其返回会赋给as关键字后的变量,而“__exit__()”方法会在with语句块退出后自动调用。

class OpenFileDemo(object):
    def __init__(self, filename):
        self.filename = filename
 
    def __enter__(self):
        self.f = open(self.filename, 'a+')
        return self.f
 
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.f.close()
 
with OpenFileDemo('test.txt') as f:
    f.write('Foo\n')

异常处理

注意到上面的“__exit__()”方法带了三个参数,他们就是用来做异常处理的,大部分情况下,我们希望with语句中遇到异常最后被抛出,但也有时,我们想处理这些异常。“__exit__()”方法中的exc_type、exc_val、exc_tb分别是异常类型、异常值、异常Traceback。当你处理完异常后,你可以让“__exit__()”方法返回True,此时改异常就不会再被抛出,如

def __exit__(self, exc_type, exc_val, exc_tb):
        self.f.close()
        if exc_type != SyntaxError:
            return True
        return False   # Only raise exception when SyntaxError

现在,如果遇到SyntaxError的化,异常会被正常抛出,而其他异常都会被忽略。

contextlib模块

contextlib是Python的一个模块提供一些简单的上下文管理器的功能

CLOSING()方法

如果说with语句块在退出时会自动调用“__exit__()”方法的话,那用了“contextlib.closing()”的with语句块则在退出时会自动调用“close()”方法。如:

import contextlib
 
class Resource(object):
    def open(self):
        print 'Open Resource'
 
    def close(self):
        print 'Close Resource'
 
with contextlib.closing(Resource()) as r:
    r.open()

程序退出后,会打印

Open Resource
Close Resource

说明Resource类创建的对象被赋给了as关键字后面的变量r,而with语句块退出时,自动调用了“r.close()”方法。

CONTEXTMANAGER装饰器

“@contextlib.contextmanager”是一个装饰器,由它修饰的方法会有两部分构成,中间由yield关键字分开。由此方法创建的上下文管理,在代码块执行前会先执行yield上面的语句,在代码块执行后在执行yield的下面语句。如:

import contextlib
import time
 
@contextlib.contextmanager
def timeit():
    start = time.time()
    yield
    end = time.time()
    usedTime = (end - start) * 1000
    print 'Use time %d ms' % usedTime
 
with timeit():
    time.sleep(1)

这个“timeit()”方法实现了一个计时器,它会计算由它生成的with语句块执行时间,可以看出,yield上面的语句就是如同之前介绍过的“__enter__()” 方法。而yield部分就是with语句块中的代码。如果yield后面带参数的话,就可以用as关键字赋值给后面的变量,如:

@contextlib.contextmanager
def timeit():
    start = time.time()
    yield start
    #...
 
with timeit() as starttime:
    print starttime
    #...

需要注意到是“@contextlib.contextmanager”不像之前的“__exit__()”方法,遇到异常也会执行。也就是with语句块抛出异常的话,yield后面的代码将不会被执行。所以必要时需要对yield语句使用“try-fibally”。

相关文章
最新推荐