Python函数-上下文管理器

这篇具有很好参考价值的文章主要介绍了Python函数-上下文管理器。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

函数-上下文管理器

上下文管理协议(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__() 方法。

01 with语句

with是从Python2.5引入的一个新的语法,它是一种上下文管理协议的具体实现,目的在于从流程图中把 try、except 和finally 关键字和 资源分配释放相关代码统统去掉,简化try….except….finlally的处理流程。

with通过__enter__方法初始化,然后在__exit__中做善后以及处理异常。 所以使用with处理的对象必须有__enter__()和__exit__()这两个方法。

其中__enter__()方法在语句体(with语句包裹起来的代码块)执行之前进入运行,exit()方法在语句体执行完毕退出后运行。

with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。

With语句的基本语法格式:

with expression [as target]:
    with_body

'''
参数说明:
expression:是一个需要执行的表达式;
target:是一个变量或者元组,存储的是expression表达式执行返回的结果,可选参数。            
with_body:是一段语句体。
'''

02 应用场景

有一些任务,可能事先需要设置,事后做清理工作。对于这种场景,Python的with语句提供了一种非常方便的处理方式。一个很好的例子是文件处理,用户需要获取一个文件句柄,从文件中读取数据,然后关闭文件句柄。

  • 文件操作
  • 进程线程之间互斥对象
  • 资源的加锁和解锁
  • 支持上下文其他对象

03 示例

# 不用with语句打开文件
f = open("test.txt")
print(f.read())
f.close()

'''
这里有两个问题:
一是可能忘记关闭文件句柄; 
二是文件读取数据发生异常,没有进行任何处理。
'''
# 使用异常处理语句增强文件操作          
try:
    f = open('test.txt') 
    print(f.read())
except:
    print('fail to open') 
finally:
     f.close()           
'''
这里有一个问题:
虽然这段代码运行良好,但是太冗长
'''
# 使用with上下文管理器          
with open('test.txt') as f:
    print(f.read())          
'''
有更优雅的语法,
还可以很好的处理上下文环境产生的异常。
'''

with open('test.txt') as f:
    print(f.read())
    
print(f.closed)   # 返回True,说明文件句柄已经被自动关闭

04 with工作原理

紧跟with关键字后面的表达式会被求值,表达式运算之后返回一个对象,该对象必须包含两个方法:enter()和__exit__()。表达式运算之后,会调用对象的__enter__()方法,这个方法的返回值将被赋值给as关键字后面的变量。

当with包含的代码块全部被执行完之后,再调用前面返回对象的__exit__()方法。

针对上面示例,当打开文件执行操作时,具体流程如下:

  1. with语句先暂存了File类的__exit__⽅法

  2. 然后它调⽤File类的__enter__⽅法

  3. __enter__⽅法打开⽂件并返回给with语句

  4. 打开的⽂件句柄被传递给 f 参数

  5. 我们使⽤.read()来读取⽂件

  6. with语句调⽤之前暂存的__exit__⽅法

  7. __exit__⽅法关闭了⽂件

#示例1,演示上下文运行全流程:
class Test:
    def __enter__(self):
        print("开始运行上下文")

    def __exit__(self, type, value, trace):
        print("结束运行上下文")


with Test():
    print("上下文运行中")
#示例2,如果with表达式运算结果不是上下文管理器,with将抛出异常:
class Test:
    pass


with Test():
    print("上下文运行中") 

'''
Traceback (most recent call last):
  File "d:/www_vs/test2.py", line 8, in <module>
    with test():
AttributeError: __enter__
'''
#示例3,完善上下文管理器
class Test:
    name = "上下文管理器"
    def __enter__(self):
        return self

    def __exit__(self, type, value, trace):
        print(type)
        print(value)        
        print(trace)  

with Test() as t:
    print(t.name)
    raise
'''
上下文管理器
<class 'RuntimeError'>
No active exception to reraise
<traceback object at 0x02BA7988>
Traceback (most recent call last):
  File "d:/www_vs/test3.py", line 13, in <module>
    raise
RuntimeError: No active exception to reraise
'''

通过示例3,可以看到__enter__()方法被执行, enter()方法必须包含1个位置参数。该方法的返回值是this,赋值给变量t。

t表示Test的实例对象,执行with代码块,打印实例对象的属性name。然后,使用raise主动抛出异常。

exit()方法被调用。该方法必须包含4个位置参数。with真正强大之处是它可以处理异常。可能你已经注意到__exit__方法后面三个参数:type、 val 和 trace。 这些参数在异常处理中相当有用,type表示异常类型,val表示异常的具体信息,trace表示具体错误细节。

实际上,如果在with后面的代码块抛出任何异常时,exit() 方法被执行,与之关联的type、value和stack trace传给 exit() 方法,因此抛出的异常信息会被打印出来了。

在开发库时,清理资源、 关闭文件等等操作,都可以放在 exit 方法中完成。另外,exit 还可以进行异常的监控和处理。

注意,如果要跳过一个异常,只需要__exit__返回True即可。如果返回False,将抛出异常。

# 下面示例将跳过所有的TypeError,而让其他异常正常抛出。
class Test:
    name = "上下文管理器"
    def __enter__(self):
        return self

    def __exit__(self, type, value, trace):
        return isinstance(value, TypeError) 

with Test() as t:
    print(t.name)
    raise TypeError()

总之,with-as表达式极大的简化了每次写finally的工作,这对保持代码的优雅性是有极大帮助的。如果有多个项,还可以如下面方式进行编写。

with open("test1.txt", "w") as f1, open('test2.txt', "w") as f2:
    f1.write("test1")
    f2.write("test2")            

因此,Python的with语句提供了一个有效的机制,让代码更简练,同时在异常产生时,清理工作更简单。

当异常发⽣时,with语句会采取哪些步骤。

  1. 它把异常的type,value和traceback传递给__exit__⽅法。

  2. 它让__exit__⽅法来处理异常。

  3. 如果__exit__返回的是True,那么这个异常就被优雅地处理了。

  4. 如果__exit__返回的是True以外的任何东西,那么这个异常将被with语句抛出。

05 应用

自定义打开文件
class myOpen(object):
    def __init__(self,filename):
        self.handle=open(filename)
        print( "Resource:%s"%filename )
    def __enter__(self):
        print( "[enter %s]: 分配资源."%self.handle )
        return self.handle  # 可以返回不同的对象
    def __exit__(self,exc_type,exc_value,exc_trackback):
        print( "[exit %s]: 释放资源." %self.handle )
        if exc_trackback is None:
            print( "[exit %s]: 无异常退出."%self.handle )
            self.handle.close()
        else:
            print( "[exit %s]: 包含异常的退出."%self.handle )
        return False # 可以省略,缺省的None也是被看做是False


with myOpen('test1.txt') as fp:
    for line in fp.readlines():
        print( line , " " )           
          
'''            
输出结果:         
Resource:test1.txt
[enter <_io.TextIOWrapper name='test1.txt' mode='r' encoding='cp936'>]: 分配资源.
1 
2  
[exit <_io.TextIOWrapper name='test1.txt' mode='r' encoding='cp936'>]: 释放资源.
[exit <_io.TextIOWrapper name='test1.txt' mode='r' encoding='cp936'>]: 无异常退出. 
'''

myOpen中的__enter__() 返回的是自身的引用,这个引用可以赋值给 as 子句中的fp变量; 返回值的类型可以根据实际需要设置为不同的类型,不必是上下文管理器对象本身。

exit() 方法中对变量exc_trackback进行检测,如果不为 None,表示发生了异常,返回 False 表示需要由外部代码逻辑对异常进行处理; 如果没有发生异常,缺省的返回值为 None,在布尔环境中也是被看做 False,但是由于没有异常发生,exit() 的三个参数都为 None,上下文管理代码可以检测这种情况,做正常处理。exit()方法的3个参数,分别代表异常的类型、值、以及堆栈信息。

自定义上下文类
class Mycontex(object):
    def __init__(self,name):
        self.name=name
    def __enter__(self):
        print("进入enter")
        return self
    def do_self(self):
        print(self.name)
    def __exit__(self,exc_type,exc_value,traceback):
        print("退出exit")
        print(exc_type,exc_value)

with Mycontex('test') as mc:
    mc.do_self()            
            
'''
输出结果:
进入enter
test
退出exit
None None
'''

自定义类必须包含上述几个方法才能正确使用with关键字。即使程序发生了错误,Python解释器终止了程序,但是我们的类 还是顺利关闭了。

06 基于⽣成器的实现

还可以⽤装饰器(decorators)和⽣成器(generators)来实现上下⽂管理器。

Python有个contextlib模块专门⽤于这个⽬的。我们可以使⽤⼀个⽣成器函数来实现⼀ 个上下⽂管理器,⽽不是使⽤⼀个类。

from contextlib import contextmanager

@contextmanager
def open_file(name):
    f = open(name, 'w')
    yield f
    f.close()

这个实现⽅式看起来更加直观和简单。然⽽,这个⽅法需要关于⽣成器、yield 和装饰器的⼀些知识。在这个例⼦中我们还没有捕捉可能产⽣的任何异常。它的⼯作⽅式 和之前的⽅法⼤致相同。

简单剖析下这个⽅法。

  1. Python解释器遇到了yield关键字。因为这个缘故它创建了⼀个⽣成器⽽不是⼀个 普通的函数。

  2. 因为这个装饰器,contextmanager会被调⽤并传⼊函数名(open_file)作为 参数。

  3. contextmanager函数返回⼀个以GeneratorContextManager对象封装过的 ⽣成器。

  4. 这个GeneratorContextManager被赋值给open_file函数,我们实际上是在 调⽤GeneratorContextManager对象。

那现在我们既然知道了所有这些,我们可以⽤这个新⽣成的上下⽂管理器了,像这样:文章来源地址https://www.toymoban.com/news/detail-658864.html

with open_file('some_file') as f:
    f.write('hola!')           

到了这里,关于Python函数-上下文管理器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Python教程(27)——如何使用Python中的上下文管理器

    当我们在编写代码时,经常会遇到需要管理资源的情况,比如打开和关闭文件,如果遇到了一些异常情况,我们需要关闭资源,不然会导致资源泄露,虽然我们可以通过手动的方式来关闭,但如果有多个异常情况需要考虑的话,万一不小心漏了一处,就芭比Q了。所以,如果有

    2024年02月19日
    浏览(37)
  • Python高级语法:with语句和上下文管理器

    1.文件操作说明: ①文件使用完后必须关闭。 ②因文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的。 例如:  2. 存在的安全隐患: ① 由于文件读写时都有可能产生IOError,一旦出错,后面的f.close()就不会调用。 例如: 运行结果: 3.try…except…

    2024年02月04日
    浏览(41)
  • 【 Python 全栈开发 - WEB开发篇 - 38 】property和上下文管理器

    Python 是一个简洁而强大的编程语言,通过提供各种特性和工具,使开发人员能够更高效地进行编程。其中一个非常有用的特性是 @property 装饰器,它允许我们在类中定义属性并自定义其行为。 @property 装饰器用于将一个方法转换为类的属性。使用 @property 装饰器,我们可以像访

    2024年02月13日
    浏览(45)
  • 【python】flask执行上下文context,请求上下文和应用上下文原理解析

    ✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN新星创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开

    2024年03月26日
    浏览(38)
  • 无限上下文,多级内存管理!突破ChatGPT等大语言模型上下文限制

    目前,ChatGPT、Llama 2、文心一言等主流大语言模型,因技术架构的问题上下文输入一直受到限制,即便是Claude 最多只支持10万token输入,这对于解读上百页报告、书籍、论文来说非常不方便。 为了解决这一难题,加州伯克利分校受操作系统的内存管理机制启发,提出了MemGPT。

    2024年02月06日
    浏览(44)
  • 〖大前端 - 基础入门三大核心之JS篇(51)〗- 面向对象之认识上下文与上下文规则

    说明:该文属于 大前端全栈架构白宝书专栏, 目前阶段免费 , 如需要项目实战或者是体系化资源,文末名片加V! 作者:哈哥撩编程,十余年工作经验, 从事过全栈研发、产品经理等工作,目前在公司担任研发部门CTO。 荣誉: 2022年度博客之星Top4、2023年度超级个体得主、谷

    2024年02月05日
    浏览(39)
  • 学习笔记整理-面向对象-02-认识函数的上下文

    什么是上下文 函数的上下文 函数中可以使用 this ,它表示 函数的上下文 。 与中文中\\\"这\\\"类似,函数中的this具体指代什么 必须通过调用函数时的\\\"前言后语\\\"来判断 函数中的this 函数的上下文由调用方式决定 同一个函数,用不同的形式调用它,则函数的上下文不同 情形

    2024年02月13日
    浏览(25)
  • Zustand 和 React 上下文状态管理

    Zustand 是客户端全局状态管理的一个很棒的库。它简单、快速,并且包大小小。然而,有一件事我不一定喜欢它:这些 Store 是全局性的。 但这不是全局状态管理的重点吗?要使该状态在您的应用程序中随处可用。不过当我回顾过去几年中使用 zustand 的情况时,我意识到,更多

    2024年04月25日
    浏览(29)
  • 读数据压缩入门笔记06_上下文转换

    3.3.1.1. 如果这两个符号是相同的,那么行程继续 3.3.1.2. 如果不相同,那么当前行程终止 8.6.1.1. 真实数据中普遍存在 9.3.1.1. 事实上符号之间的顺序很重要 9.3.4.1. lexicographical permutation 9.3.4.2. BWT会打乱数据流中符号的顺序,并试图让相同的符号簇彼此靠近 9.3.4.3. 找出原始数

    2024年02月09日
    浏览(40)
  • pymysql 上下文管理器控制事务提交和回滚

    示例: 从上面例子看出, 在with结构进入时,事务开始begin 在with结构退出时,会根据是否产生异常进行commit和rollback操作,并关闭连接

    2024年01月25日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包