python with as 用法_Python的with魔法

0110caa8498816a6c70698bd07ec757f.png

(全文字数: 2300, 阅读时间: 4分钟)

Python为人称道的优势之一是代码简洁性高。这个优势有时也有副作用,那就是看似简单的代码,背后可能隐含着复杂的逻辑,让人捉摸不透。有人把这种现象叫做Python魔法。今天,我们就来聊聊一个典型的Python魔法:with代码块(code block)。

一个最基本的with代码块示例如下:

with obj:    do something

大家可不要被这个示例表面的简单所迷惑。事实上,在运行时,这段代码会执行多个操作。它的实际执行过程如下:

obj.__enter__()try:    do somethingfinally:    obj.__exit__()

我们可以这样理解此过程:

  • 对变量obj使用with关键字的前提是,obj具有__enter__和__exit__两个方法。

  • 在执行"with obj"语句时,obj的__enter__方法被调用。

  • do something结束后,在退出with语句块时,obj的__exit__方法被调用。

  • 即使do something过程中遇到异常,obj的__exit__方法也会被调用。

可见,with代码块确实"外表简洁,内涵丰富"。为了更形象地说明with代码块的执行过程,构造一个完整的示例如下:

class MyContext(object):    def __init__(self):        print("init context")        def __enter__(self):        print("enter context")    def __exit__(self, type, value, traceback):        print("exit context")if __name__ == "__main__":    print("before with")    with MyContext():        print("with body")    print("after with")

执行上述脚本,控制台输出反映了with代码块的实际执行过程:

root@hzettv53:~# python with.pybefore withinit contextenter contextwith bodyexit contextafter with

至此,我们大概了解了with代码块是怎么回事。为了加深对with代码块的理解,我们需要知道__enter__和__exit__两个方法存在的背景。值得注意的是,这两个方法是成对出现的,它们一起构成了上下文管理协议(Context Management Protocol,CMP)。

支持CMP的对象叫做上下文管理器(Context Manager, CM)。CM通过实现CMP,来定义一个运行时上下文(Runtime Context);with代码块则提供了一种高效调用上下文的方式。具体来说,它凭借极简语法,帮助我们进入上下文,执行任务,然后离开上下文。这里你可能会问,究竟什么是上下文"context"呢?

context是计算机科学中的一个抽象概念,它在不同具体场合可能有不同含义。在本文的情境中,我们可以将context简单理解为执行with代码块body部分所依赖的某种资源。

基于这个理解,我们可以这样描述with代码块和上下文管理器CM存在的意义:通过with关键字,我们只在真正需要某个资源的时间和地点分配它,并且在使用完资源之后立即释放它。通俗地将,就是"用时才取,用完即还",以此实现对资源的有效隔离和高效利用。

以最常见的文件读写为例:

with open("foo.txt", "r") as f:    print(f.read())

我们的任务是读取文件foo.txt的内容。这个任务依赖的资源是文件对象(file object),它提供一系列操作文件内容的方法。通过with代码块,我们只在执行文件读取操作,即调用f.read函数的时候,才通过open函数打开文件并生成文件对象;文件读取完毕之后,调用f.close函数销毁文件对象并关闭文件。

到这里,我们介绍了with代码块和上下文管理器(CM)的基本概念和使用方法。除此之外,它们还有许多扩展用法。下面就介绍其中典型的9种。

这里说明一下,为了大家的阅读体验,我一般不喜欢在技术文章中插入大段代码。但是有时候,不去阅读和执行代码,又很难真正理解技术。以下扩展用法,大家可以结合我的github项目https://github.com/slxiao/ python-advanced/tree/master/python-context-manager中的示例来加深理解。

1,with-as:"with obj as var"和"with obj"稍有不同之处。前者除了执行obj.__enter__函数之外,还将__enter__的返回值赋给变量var。一般来说,__enter__的返回值是self(即对象自身)或者与这个对象相关的另一个对象。

2,with组合:with关键字可以同时处理多个CM。例如:

with A() as a, B() as b:    do something

3,with嵌套:with代码块中可以嵌套使用with。例如在下面的示例中,程序执行顺序为:A().__enter__ -> B().__enter__ -> B().__exit__ -> A().__exit__()。

with A():    with B():        do something

4,异步with:异步with(async with)处理的对象是异步CM。与CM不同的是,异步CM实现的是__aenter__和__aexit__两个方法,并且它们在执行过程中可以被挂起。

async with EXPR as VAR:    do something

5,with-yield:在下面的示例中,yield语句出现在with代码块中。此时,函数返回一个生成器。在调用生成器时,执行obj的初始化和进入工作;在生成器被系统垃圾回收时,执行obj的退出工作。

def yield_from():    with obj:        yeild obj

6,一次性CM:有部分CM实例化之后,只能被with代码块调用一次。如果with第二次调用,会抛出异常,除非重新实例化。

7,可重入CM:与一次性CM不同的是,可重入CM不仅可以在多个with代码块中使用,而且可以在嵌套with中使用,即可以在with代码块的body部分再次进入同一个实例化的CM。

8,可复用CM:可复用CM与可重入CM存在细微区别,它可以在多个with代码块中使用,但是不可以在嵌套with中使用。

9,异常抑制:我们知道,with的一个特点是在代码块遇到异常时,也会执行__exit__函数释放资源。进一步的,如果__exit__函数返回值设置为TRUE,那么代码块异常会被抑制,从而避免主程序退出。

以上我们介绍了with代码块和上下文管理器的1种基本用法和9种扩展性用法。那么,它们一般用在什么样的场合呢?

广义地说,当程序中存在成对出现的操作时,都可以使用上下文管理器,例如: open-close,lock-release,change-reset,enter-exit,start-stop。据此,典型的应用场景可以包括:文件操作,线程同步锁管理,数据库连接管理,软件测试的Setup/Teardown等。

上面介绍的这么多上下文管理器用法,显然不是单独一个with关键字所能够支撑的。Python标准库中的contextlib模块,提供了许多装饰器和接口函数,通过与with关键字配合使用,实现对Python上下文管理器各种用法的全面支持。

通过阅读这篇文章,大家了解到: 1) with代码块的基本用法和内涵,2)上下文管理协议和上下文管理器的概念,3) with代码块和上下文管理器的多种扩展用法,4) with代码块和上下文管理器的应用场景。

推荐阅读:

Python3: 我们不一样

Python lambda: 从入门到放弃

c442f9ac74bd238ecb40f2253ba5146a.png

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值