python中with的用法_Python中with的介绍以及高级用法

在Python2.6中引入了 with 语句,如以下代码

with open("myfile", "r") as f:

print f.readline()

相信很多的Python教程中都提到过 with 这么一个简洁的语法。如果不用with,那么我们将改写成如下形式:

f = file("myfile", "r")

try:

print f.readline()

execpt Exception:

pass

finally:

f.close()

一对比发现,with语言显得更加简洁,原因就是open对象实现了上下文管理协议(context manage), 既在class中实现了 __enter__ 和 __exit__ 两个方法,如果要自己实现一个支持上下文协议的对象,那么在这个对象中必须实现这两个方法。

现在来假设这么一种场景,代码中全局的日志级别为error,但是在调用某个方法的时候需要设置日志级别为debug,并且调用结束之后,日志级别仍然为error,代码和执行结果如下:

现在我们来分析一下代码。

执行with的时候,先调用 __enter__ 方法,当with中带有as 的时候,该方法返回的对象赋值给 a,然后执行with内部的代码块,此时日志级别已经调整到dubug等级,所以这里是可以打印出 "in debug"的,最后调用__exit__ 方法,注意到这里,__exit__有三个参数,exc_type, exc_val,exc_tb。通过debug打印出来,发现都是None。这是因为,只有with代码块里,发生异常时,这三个参数才会有值,exc_type 对应异常类,exc_val 对应异常值,exc_tb对应调用栈信息,这三个值刚好对应着发生异常时调用的sys.exc_info(),如下图

这意味着什么呢?

这就说明了,在with的__exit__方法中可以处理异常而不需要try...except, 下面举个例子。

如果你不想处理异常,而是想让程序忽略这个异常继续执行下去,那么只需要在 __exit__方法中return True就行了。

让我们把事情变得更复杂一点。

如果说想要一个对象支持上下文协议,那么这个对象必须实现__enter__ 和 __exit__两个方法,代码中或许不止一个对象需要用到上下文管理器,那不就要把全都的对象都实现了这两个方法,那岂不是显得很繁琐。所以问题来了,我们要让代码更加具有通用性。

首先,我们来分析一下问题,第一步是要得到一个支持上下文协议的对象,那么我们先定义一个基础类

class GenerateContextManager(object):

def __enter__(self):

return self

def __exit__(self, exc_type, exc_val, exc_tb):

return True

基础类定义好之后,考虑一下如何去生成这个类的实例,并添加和主要功能不相关的逻辑代码

def gen_cm_instance()

return GenerateContextManager()

执行结果在这里:

那么如何添加和主要逻辑的代码呢?这里我们把生成实例的函数改成装饰器。

class Student(object):

def __init__(self):

self.student_list = [x for x in xrange(10)]

def my_log(self):

log.debug("in Student")

class GenerateContextManager(object):

def __init__(self, func):

self.func = func

def __enter__(self):

try:

return self.func.next()

except StopIteration:

raise

def __exit__(self, exc_type, exc_val, exc_tb):

try:

self.func.next()

except StopIteration:

return

return True

def gen_wrapper(func):

@wraps(func)

def wrapper(*args, **kwargs):

return GenerateContextManager(func(*args, **kwargs))

return wrapper

@gen_wrapper

def gen_cm_instance():

log.setLevel(logging.DEBUG)

log.debug("before yield")

yield Student()

log.debug("after yield")

log.setLevel(logging.ERROR)

log.debug('before with')

with gen_cm_instance() as a:

log.debug('in debug')

log.debug(a)

a.my_log()

log.debug('after with')这里是执行的结果:

大家可以看到,在gen_cm_instance() 函数中我用了yield,为什么呢?

这是因为通过yield能够把这个函数里面的代码一分为二,从而达到从主要逻辑中分离次要逻辑的目的。关于yield的用法可以去google更多的教程。

其实,我刚刚的写法就是官方标准库中contextlib提供的一种更加简单的写法,关于context

有些时候我们只是需要它去支持上下文协议,并不需要管太多__enter__ 和 __exit__ 方法中添加了什么代码。因此标准库中有更简洁的写法,那就是contextlib,这个库的代码很简单:

def contextmanager(func): """@contextmanager decorator. Typical usage: @contextmanager def some_generator(): try: yield finally: This makes this: with some_generator() as : equivalent to this: try: = finally: """ @wraps(func) def helper(*args, **kwds): return GeneratorContextManager(func(*args, **kwds)) return helper

官方代码很简单,也很精炼,而且还告诉我们具体使用方式,所以说,当我们对某个函数或者对象如何使用不熟悉的时候,不一定要去网上找教程,很多代码,官方文档中就已经解释得很详细了。

关于上下文协议的东西就讲到这里,在标准库contextlib中还有几个其他的用法,这里就不详细解释了,感兴趣的可以自己去看源代码,反正也不是很难看懂。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值