python上下文管理器及with语句
with语句支持在一个叫上下文管理器的对象的控制下执行一系列语句,语法大概如下:
1 2 | with context as var: statements |
其中的context必须是个上下文管理器,它实现了两个方法__enter__,__exit__。
1.需求是怎么产生的
在正常的管理各种系统资源(文件、锁定和连接),在涉及到异常时通常是个棘手的问题。异常很可能导致控制流跳过负责释放关键资源的语句。
看一段简单的文件写入代码:
1 2 3 4 5 | filename = 'my_file.txt' f = open (filename, 'w' ) f.write( 'Hello ' ) f.write( 'World' ) f.close() |
如果发生了意外的情况,例如写入'World'时磁盘空间不足,就会抛出异常,那么close()语句将不会被执行。
一般的解决方案是使用try-finally语句:
1 2 3 4 5 6 7 | try : filename = 'my_file.txt' f = open (filename, 'w' ) f.write( 'Hello ' ) f.write( 'World' ) finally : f.close() |
但随着语句的增多,try-finally显然不够简洁,用with-as语句可以很简洁的实现以上功能:
1 2 3 | with open ( 'my_file' , 'w' ) as f: f.write( 'Hello ' ) f.write( 'World' ) |
这样不仅能处理出现异常的情况,而且还避免了在open一个文件后忘记了写close()方法的情况。
线程中的锁其实也实现了上下文管理协议:
1 2 3 4 5 | import threading with threading.Lock(): # 关键部分 statements # 关键部分结束 |
再也不怕忘记将锁释放了。
2.上下文管理器的实现
上下文管理器要实现__enter__和__exit__的特殊方法。
__enter__(self): 进入上下文管理器时调用此方法,其返回值将被放入with-as语句中as说明符指定的变量中。
__exit__(self,type,value,tb):离开上下文管理器调用此方法。如果有异常出现,type、value、tb分别为异常的类型、值和追踪信息。如果没有异常,
3个参数均设为None。此方法返回值为True或者False,分别指示被引发的异常得到了还是没有得到处理。如果返回False,引发的异常会被传递出上下文。
文件上下文管理协议大概是如下实现的:
1 2 3 4 5 6 7 8 9 10 11 12 13 | class OpenFile( object ): def __init__( self ,filename,mode): self .filename = filename self .mode = mode def __enter__( self ): self .f = open ( self .filename, self .mode) return self .f #作为as说明符指定的变量的值 def __exit__( self , type ,value,tb): self .f.close() return False #异常会被传递出上下文 with OpenFile( 'my_file.txt' , 'w' ) as f: f.write( 'Hello ' ) f.write( 'World' ) |
相当于
1 2 3 4 | try : 执行__enter__的内容 finally : 执行__exit__的内容 |
3.异常的处理
__exit__函数就能够拿到关于异常的所有信息(异常类型,异常值以及异常追踪信息),这些信息将帮助异常处理操作。
1 2 3 4 5 6 7 8 9 10 | class ListTrans( object ): def __init__( self ,alist): self .alist = alist def __enter__( self ): self .thecopy = list ( self .alist) return self .thecopy def __exit__( self ,exc_type,value,tb): if exc_type is None : self .alist[:] = self .thecopy return False |
没有异常发生时:
1 2 3 4 5 | alist = [] with ListTrans(alist) as working: working.append( 1 ) working.append( 2 ) print alist |
生成:
1 | [ 1 , 2 ] |
有异常发生时:
1 2 3 4 5 6 | alist = [] with ListTrans(alist) as working: working.append( 1 ) working.append( 2 ) raise RuntimeError( 'we are hosed' ) print alist |
生成:
1 | RuntimeError: we are hosed |
alist无变化。
可以捕捉异常:
1 2 3 4 5 6 7 8 9 | alist = [] try : with ListTrans(alist) as working: working.append( 1 ) working.append( 2 ) raise RuntimeError( 'we are hosed' ) except RuntimeError as e: print e print alist |
生成:
1 2 | we are hosed [] |
当然,也可以简单的将__exit__的返回值设为True来忽略异常。
4.contextmanager装饰器
@contextmanager
contextlib模块的contextmanager装饰器可以更方便的实现上下文管理器。
任何能够被yield关键词分割成两部分的函数,都能够通过装饰器装饰的上下文管理器来实现。任何在yield之前的内容都可以看做在代码块执行前的操作,
而任何yield之后的操作都可以放在exit函数中。
1 2 3 4 5 6 7 8 9 10 11 | from contextlib import contextmanager @contextmanager def listTrans(alist): thecopy = list (alist) yield thecopy alist[:] = thecopy alist = [] with listTrans(alist) as working: working.append( 1 ) working.append( 2 ) print alist |
yield返回的值相当于__enter__的返回值。
要注意的是,这不是异常安全的写法,也就是说,当出现异常时,yield后的语句是不会执行的,想要异常安全,可用try捕捉异常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | from contextlib import contextmanager @contextmanager def listTrans(alist): thecopy = list (alist) try : yield thecopy except RuntimeError: pass alist[:] = thecopy alist = [] with listTrans(alist) as working: working.append( 1 ) working.append( 2 ) raise RuntimeError |
nested与closing
contextlib模块还有两个好玩的方法:nested,closing。
nested:用来更方便的减少嵌套写法:
当要嵌套的写上下文管理器时:
1 2 3 | with open ( 'toReadFile' , 'r' ) as reader: with open ( 'toWriteFile' , 'w' ) as writer: writer.writer(reader.read()) |
可以用nested简化写法:
1 2 3 | with contextlib.nested( open ( 'fileToRead.txt' , 'r' ), open ( 'fileToWrite.txt' , 'w' )) as (reader, writer): writer.write(reader.read()) |
python2.7后nested就过时了:
1 2 | with open ( 'fileToRead.txt' , 'r' ) as reader, open ( 'fileToWrite.txt' , 'w' ) as writer: writer.write(reader.read()) |
closing(object):创建上下文管理器,在执行过程离开with语句时自动执行object.close():
1 2 3 4 5 6 7 | class Door( object ) : def open ( self ) : print 'Door is opened' def close( self ) : print 'Door is closed' with contextlib.closing(Door()) as door : door. open () |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
· Manus的开源复刻OpenManus初探