python的with语句浅析

with语句是在python2.5开始引进的一种与异常处理相关的功能。在讲with语句之前,先说说上下文管理器,因为使用上下文管理器最广泛的就是with语句了。

基本概念

上下文管理协议(Context Management Protocol):包含方法 __enter__() 和 __exit__(),支持该协议的对象要实现这两个方法。

上下文管理器(Context Manager):支持上下文管理协议的对象,这种对象实现了__enter__() 和 __exit__() 方法。上下文管理器定义执行 with 语句时要建立的运行时上下文,负责执行 with 语句块上下文中的进入与退出操作。通常使用 with 语句调用上下文管理器,也可以通过直接调用其方法来使用。

运行时上下文(runtime context):由上下文管理器创建,通过上下文管理器的 __enter__() 和__exit__() 方法实现,__enter__() 方法在语句体执行之前进入运行时上下文,__exit__() 在语句体执行完后从运行时上下文退出。with 语句支持运行时上下文这一概念。

上下文表达式(Context Expression):with 语句中跟在关键字 with 之后的表达式,该表达式要返回一个上下文管理器对象。

语句体(with-body):with 语句包裹起来的代码块,在执行语句体之前会调用上下文管理器的 __enter__() 方法,执行完语句体之后会执行 __exit__() 方法。

上下文管理器

这里构造一个简单的上下文管理器的例子,以理解__enter__() 和__exit__() 方法

from math import sqrt, pow

class Point(object):
    def __init__(self, x, y):
        print('initialize x and y')
        self.x, self.y = x, y

    def __enter__(self):
        print("Entering context")
        return self

    def __exit__(self, type, value, traceback):
        print("Exiting context")

    def get_distance(self):
        distance = sqrt(pow(self.x, 2) + pow(self.y, 2))
        return distance

if __name__ == "__main__":
    with Point(3,4) as pt:
        print('distance ' + str(pt.get_distance()))

#output
initialize x and y
Entering context
distance 5.0
Exiting context

上面的代码定义了一个 Point 类,并实了 __enter__() 和 __exit__() 方法,我们还定义了 get_distance 方法,用于返回点到原点的距离。

通过with语句调用上下文管理器,具体执行过程如下:

<1>Point(3, 4) 生成了一个上下文管理器;

<2>调用上下文管理器的 __enter__() 方法,并将 __enter__() 方法的返回值赋给 as 字句中的变量 pt;

<3>执行语句体(指 with 语句包裹起来的代码块)内容,输出 distance;

<4>不管执行过程中是否发生异常,都执行上下文管理器的 __exit__() 方法。__exit__() 方法负责执行『清理』工作,如释放资源,关闭文件等。如果执行过程没有出现异常,或者语句体中执行了语句 break/continue/return,则以 None 作为参数调用 __exit__(None, None, None);如果执行过程中出现异常,则使用 sys.exc_info 得到的异常信息为参数调用 __exit__(exc_type, exc_value, exc_traceback);

<5>出现异常时,如果 __exit__(type, value, traceback) 返回 False 或 None,则会重新抛出异常,让 with 之外的语句逻辑来处理异常;如果返回 True,则忽略异常,不再对异常进行处理;

上面的 with 语句执行过程没有出现异常,我们再来看出现异常的情形:

if __name__ == "__main__":
    with Point(3,4) as pt:
        print('distance ' + str(pt.get_lengh()))
#output
initialize x and y
Entering context
Exiting context
并且有报错信息

在我们的例子中,__exit__ 方法返回的是 None(如果没有 return 语句那么方法会返回 None)。因此,with 语句抛出了那个异常。我们对 __exit__ 方法做一些改动,让它返回 True。

def __exit__(self, type, value, traceback):
    print("Exiting context")
    return True

#output
initialize x and y
Entering context
Exiting context

可以看到,由于 __exit__ 方法返回了 True,因此没有异常会被 with 语句抛出。

with语句是怎么执行的?

context_manager = context_expression
exit = type(context_manager).__exit__  
value = type(context_manager).__enter__(context_manager)
exc = True   # True 表示正常执行,即便有异常也忽略;False 表示重新抛出异常,需要对异常进行处理
try:
    try:
        target = value  # 如果使用了 as 子句
        with-body     # 执行 with-body
    except:
        # 执行过程中有异常发生
        exc = False
        # 如果 __exit__ 返回 True,则异常被忽略;如果返回 False,则重新抛出异常
        # 由外层代码对异常进行处理
        if not exit(context_manager, *sys.exc_info()):
            raise
finally:
    # 正常退出,或者通过 statement-body 中的 break/continue/return 语句退出
    # 或者忽略异常退出
    if exc:
        exit(context_manager, None, None, None) 
    # 缺省返回 None,None 在布尔上下文中看做是 False

<1>执行 context_expression,生成上下文管理器 context_manager

<2>调用上下文管理器的 __enter__() 方法;如果使用了 as 子句,则将 __enter__() 方法的返回值赋值给 as 子句中的 target(s)

<3>执行语句体 with-body

<4>不管是否执行过程中是否发生了异常,执行上下文管理器的 __exit__() 方法,__exit__() 方法负责执行“清理”工作,如释放资源等。如果执行过程中没有出现异常,或者语句体中执行了语句 break/continue/return,则以 None 作为参数调用 __exit__(None, None, None) ;如果执行过程中出现异常,则使用 sys.exc_info 得到的异常信息为参数调用 __exit__(exc_type, exc_value, exc_traceback)

<5>出现异常时,如果 __exit__(type, value, traceback) 返回 False,则会重新抛出异常,让with 之外的语句逻辑来处理异常,这也是通用做法;如果返回 True,则忽略异常,不再对异常进行处理

内建对象使用with语句

除了自定义上下文管理器,Python 中也提供了一些内置对象,可直接用于 with 语句中,比如最常见的文件操作。

传统的文件操作经常使用 try/finally 的方式,比如:

file = open('somefile', 'r')try:
    for line in file:
        print(line)
finally:
    file.close()     # 确保关闭文件

将上面的代码改用 with 语句:

with open('somefile', 'r') as file:
    for line in file:
        print(line)

可以看到,通过使用 with,代码变得很简洁,而且即使处理过程发生异常,with 语句也会确保我们的文件被关闭。

contextlib 模块

除了在类中定义 __enter__ 和 __exit__ 方法来实现上下文管理器,我们还可以通过生成器函数(也就是带有 yield 的函数)结合装饰器来实现上下文管理器,Python 中自带的 contextlib 模块就是做这个的。

contextlib 模块提供了三个对象:装饰器 contextmanager、函数 nested 和上下文管理器 closing。其中,contextmanager 是一个装饰器,用于装饰生成器函数,并返回一个上下文管理器。需要注意的是,被装饰的生成器函数只能产生一个值,否则会产生 RuntimeError 异常。

下面我们看一个简单的例子:

from contextlib import contextmanager

@contextmanager
def point(x, y):
    print('before yield')
    yield x * x + y * y
    print('after yield')

with point(3, 4) as value:
    print('value is: %s' % value)

#output
before yield
value is: 25
after yield

可以看到,yield 产生的值赋给了 as 子句中的 value 变量。

另外,需要强调的是,虽然通过使用 contextmanager 装饰器,我们可以不必再编写 __enter__ 和 __exit__ 方法,但是『获取』和『清理』资源的操作仍需要我们自己编写:『获取』资源的操作定义在 yield 语句之前,『释放』资源的操作定义在 yield 语句之后。

总结

<1>上下文管理器是支持上下文管理协议的对象,也就是实现了 __enter__ 和 __exit__ 方法。

<2>通常,我们使用 with 语句调用上下文管理器。with 语句尤其适用于对资源进行访问的场景,确保执行过程中出现异常情况时也可以对资源进行回收,比如自动关闭文件等。

<3>__enter__ 方法在 with 语句体执行前调用,with 语句将该方法的返回值赋给 as 字句中的变量,如果有 as 字句的话。

<4>__exit__ 方法在退出运行时上下文时被调用,它负责执行『清理』工作,比如关闭文件,释放资源等。如果退出时没有发生异常,则 __exit__ 的三个参数,即 type, value 和 traceback 都为 None。如果发生异常,返回 True 表示不处理异常,否则会在退出该方法后重新抛出异常以由 with 语句之外的代码逻辑进行处理。

参考博文

http://funhacks.net/explore-python/Advanced-Features/context.html

https://www.ibm.com/developerworks/cn/opensource/os-cn-pythonwith/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值