首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

Python语法学习记录(1):关于WITH用法总结

  • 25-03-03 18:02
  • 2432
  • 12978
blog.csdn.net

呆呆象呆呆的所有博客目录

解析及使用场景

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

在开发的过程中,会有很多对象在使用之后,是需要执行一条或多条语句来进行关闭,释放等操作的,例如上面说的的文件,还有数据库连接,锁的获取等,这些收尾的操作会让代码显得累赘,也会造成由于程序异常跳出后,没有执行到这些收尾操作,而导致一些系统的异常,还有就是很多程序员会忘记写上这些操作--!--!,为了避免这些错误的产生,with语句就被生产出来了。with语句的作用就是让程序员不用写这些收尾的代码,并且即使程序异常也会执行到这些代码(finally的作用)

不使用with时:

f = open("test.txt")
try:
    for line in f.readlines():
        print(line)
finally:
    f.close()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

不管文件操作有没有出现异常,try/finally中的finally语句都会执行,从而保证文件的正确关闭。但是很显然Python的设计者们并没有满足于此,他们以希望更简洁更优美的形式来实现资源的清理,而且希望这种清理工作不需要暴露给使用者,所以便出现了with语句。

语法结构:

with expression [as variable]:
    with-block
  • 1
  • 2

语法说明:

​ expression是上下文管理器。上下文管理器定义执行 with 语句时要建立的运行时上下文,负责执行 with 语句块上下文中的进入与退出操作。通常使用 with 语句调用上下文管理器,也可以通过直接调用其方法来使用。

​ [as variable]是可选的,如果指定了as variable说明符,则variable是上下文管理器expression调用__enter__()函数返回的对象。所以,下面例子中的f并不一定就是expression,而是expression.__enter__()的返回值,至于expression.__enter__()返回什么就由这个函数来决定了。

​ with-block是执行语句,with-block执行完毕时,with语句会自动进行资源清理,对应下面面例子就是with语句会自动关闭文件。with 语句包裹起来的代码块,在执行语句体之前会调用上下文管理器的 __enter__() 方法,执行完语句体之后会执行 __exit__() 方法。

原理说明:

​ expression是一个上下文管理器,其包含了__enter__()和__exit__()两个函数。当我们调用一个with语句时,执行过程如下:

​ 1.首先生成一个上下文管理器expression,在下面例子中with语句首先以“test.txt”作为参数生成一个上下文管理器open(“test.txt”)。

​ 2.然后执行expression.__enter__()。如果指定了[as variable]说明符,将__enter__()的返回值赋给variable。上例中open(“test.txt”).__enter__()返回的是一个文件对象给f。

​ 3.执行with-block语句块。例中执行读取文件的操作。

​ 4.执行expression.__exit__(),在__exit__()函数中可以进行资源清理工作。上面例子中就是执行文件的关闭操作。

​ 例子:

with open("text.txt") as f:
    for line in f.readlines()
    print(line)
  • 1
  • 2
  • 3

其他用法:

with适用于上下文管理器的调用

with支持 threading、decimal等模块,管理锁、连接

当然我们也可以自己定义可以给with调用的上下文管理器

#管理锁
import  threading
lock = threading.lock()
with lock:
    #执行一些操作
    pass
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

如果有多个项,我们可以这么写:

with open("x.txt") as f1, open('xxx.txt') as f2:
    do something with f1,f2
  • 1
  • 2

上下文管理器

名词解释

​ 在上文中我们提到with语句中的上下文管理器。with语句可以如此简单但强大,主要依赖于上下文管理器。那么什么是上下文管理器?

上下文管理协议(Context Management Protocol):

​ 包含方法 __enter__() 和 __exit__(),支持该协议的对象要实现这两个方法。上下文协议就是一个类要实现__enter__()和__exit__()两个方法。

上下文管理器(Context Manager):

​ 上下文管理器就是实现了上下文协议的类,而一个类只要实现了__enter__()和__exit__(),我们就称之为上下文管理器。

如果我们要自定义一个上下文管理器,只需要定义一个类并且是实现__enter__()和__exit__()即可。下面通过一个简单的例子是演示如果新建自定义的上下文管理器,我们以数据库的连接为例。在使用数据库时,有时要涉及到事务操作。数据库的事务操作当调用commit()执行sql命令时,如果在这个过程中执行失败,则需要执行rollback()回滚数据库,通常实现方式可能如下:

运行时上下文(runtime context):

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

__enter__():主要执行一些环境准备工作,同时返回一资源对象。如果上下文管理器open(“test.txt”)的__enter__()函数返回一个文件对象。

__exit__():完整形式为__exit__(type, value, traceback),这三个参数和调用sys.exec_info()函数返回值是一样的,分别为异常类型、异常信息和堆栈。如果执行体语句没有引发异常,则这三个参数均被设为None。否则,它们将包含上下文的异常信息。__exit__()方法返回True或False,分别指示被引发的异常有没有被处理,如果返回False,引发的异常将会被传递出上下文。如果__exit__()函数内部引发了异常,则会覆盖掉执行体的中引发的异常。处理异常时,不需要重新抛出异常,只需要返回False,with语句会检测__exit__()返回False来处理异常。

上下文表达式(Context Expression):

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

使用类定义上下文管理器

class Sample:
    def __enter__(self):
        print ("此时程序在 __enter__()")
        #通常的文件句柄就在此处提取  然后进行返回
        return "the_return_value"
    def fun(self):
		#假设函数的其他功能模块
        print("此时程序在 __fun__()")
    def __exit__(self,type,value,trace):
        print ("此时程序在 __exit__()")
        
def get_sample1():
    return Sample()

def get_sample2():
    sample2 = Sample()
    sample2.fun()
    return sample2  
#返回这个类 之后as赋值进入 sample   之后会先后调用enter和exit
 
with get_sample1() as sample:
    print ("return_valeu_to_sample:", sample)

with get_sample2() as sample:
    print ("return_valeu_to_sample:", sample)
    

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

运行结果

正如你看到的:

with语句1中

  1. 首先__enter__()方法被执行
  2. __enter__()方法返回的值 - 这个例子中是"the_return_value",赋值给变量sample
  3. 直接执行with语句下的代码块,打印变量sample的值为 “the_return_value”
  4. __exit__()方法被调用

with语句2中

  1. 首先执行 get_sample2()中间的实例化,然后执行fun函数,将实例化对象返回给sample
  2. 进入with的标准化执行阶段:__enter__()方法被执行
  3. __enter__()方法返回的值 - 这个例子中是"the_return_value",赋值给变量sample
  4. 直接执行with语句下的代码块,打印变量sample的值为 “the_return_value”
  5. __exit__()方法被调用

异常的处理

注意到Sample类的 __exit__ ()方法有三个参数 value, type 和 trace。 这些参数在异常处理中相当有用。我们来改一下代码,看看具体如何工作的。

class Sample:
    def __enter__(self):
        print ("此时程序在 __enter__()")
        #通常的文件句柄就在此处提取  然后进行返回
        return self
    def __exit__(self,type,value,trace):
        print ("此时程序在  __exit__()")
        print ("type:",type)
        print ("value:",value)
        print ("trace:",trace)
    def do_something(self):
        #执行这个程序是正常情况下会报错,但在with中会将错误信息进行返回到exit中
        print("此时程序在  do_something")
        bar = 1/0
        return bar + 10
            
with Sample() as sample:
    sample.do_something()

print("程序仍然可以继续执行")#这句话不会执行    
    

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

运行结果:

[外链图片转存失败(img-LVSEPYsu-1567578270329)(https://i.loli.net/2019/03/22/5c943d2c4a069.png)]

这个例子中,with后面的get_sample()变成了Sample()。这没有任何关系,只要紧跟with后面的语句所返回的对象有 __enter__() 和 __exit__() 方法即可。此例中,Sample()的 __enter__() 方法返回新创建的Sample对象(return self),并赋值给变量sample。

实际上,在with后面的代码块抛出任何异常时,__exit__() 方法被执行。正如例子所示,异常抛出时,与之关联的type,value和stack trace传给 __exit__() 方法,因此抛出的ZeroDivisionError异常被打印出来了。开发库时,清理资源,关闭文件等等操作,都可以放在__exit__() 方法当中。

针对异常的处理

另外,__exit__ 除了用于tear things down,还可以进行异常的监控和处理,注意后几个参数。要跳过一个异常,只需要返回该函数True即可。

下面的样例代码跳过了指定的TypeError,而让其他异常正常抛出。

PS如何跳过所有typeerror

class Sample:
    def __enter__(self):
        print ("此时程序在 __enter__()")
        #通常的文件句柄就在此处提取  然后进行返回
        return self
    def __exit__(self,type,value,trace):
        print ("此时程序在  __exit__()")
        print ("type:",type)
        print ("value:",value)
        print ("trace:",trace)
        print  (isinstance(value, ZeroDivisionError))
        return isinstance(value, ZeroDivisionError)
    def do_something(self):
        #执行这个程序是正常情况下会报错,但在with中会将错误信息进行返回到exit中
        print("此时程序在  do_something")
        bar = 1/0
        return bar + 10
            
with Sample() as sample:
    sample.do_something()

print("程序仍然可以继续执行")    


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

运行结果

使用生成器定义上下文管理器

from contextlib import contextmanager

#@表示修饰符,可以在模块或者类的定义层内对函数进行修饰。出现在函数定义的前一行,不允许和函数定义在同一行。
#此处使用contextmanager作为类似于魔板的功能

@contextmanager
def demo():
    print ('这里的代码相当于__enter__里面的代码')
    yield ('i ma value')
    print ('这里的代码相当于__exit__里面的代码')

with demo() as value:
    print  (value)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

自定义支持 closing 的对象

class closing(object):
    def __init__(self, thing):
        print ('这里的代码相当于closing__init__里面的代码')
        self.thing = thing
    def __enter__(self):
        print ('这里的代码相当于closing__enter__里面的代码')
        #运行到此处A()已经赋值进入self.thing  所以返回的self.thing即为A()
        return self.thing
    
    def __exit__(self, *exc_info):
        print ('这里的代码相当于closing__exit__里面的代码')
        self.thing.close()

class A():
    def __init__(self):
        print ('这里的代码相当于A()__init__里面的代码')
        self.thing=open('file_name','w')
    def f(self):
        print ('运行函数')
    def close(self):
        print ('这里的代码相当于A()close里面的代码')
        self.thing.close()

with closing(A()) as a:
    print("进入with表达式")
    a.f()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

总结

总之,with-as表达式极大的简化了每次写finally的工作,这对保持代码的优雅性是有极大帮助的。Python的with语句是提供一个有效的机制,让代码更简练,同时在异常产生时,清理工作更简单。

关于contextlib模块的使用1

关于contextlib模块的使用2

文章知识点与官方知识档案匹配,可进一步学习相关知识
Python入门技能树进阶语法with-as416623 人正在系统学习中
注:本文转载自blog.csdn.net的呆呆象呆呆的文章"https://blog.csdn.net/qq_41554005/article/details/88735081"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2491) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top