Python 中的 with 语句与上下文管理器

Stella981
• 阅读 543

对象,是 Python 对数据的抽象概念。Python 中的所有数据都是以对象或对象间的关系来实现的。(某种意义上,代码也是由对象来实现的,这与冯·诺依曼的“stored program computer”模型相一致)。每个对象都有 id、type、和 value。对象的 id 从创建之时起就不再改变,你可以把它想象成对象在内存中的地址。is 运算符就是比较的两个对象的 id,或者你也可以通过内建的 id() 函数来直接查看。

正文:

  • with 语句

with 语句是被设计用来简化“try / finally”语句的。通常的用处在于共享资源的获取和释放,比如文件、数据库和线程资源。它的用法如下:

with context_exp [as var]:

        with_suit

with 语句也是复合语句的一种,就像 if、try 一样,它的后面也有个“:”,并且紧跟一个缩进的代码块 with_suit。context_exp 表达式的作用是提供一个上下文管理器(Context Manager),整个 with_suit 代码块都是在这个上下文管理器的运行环境下执行的。context_exp 可以直接是一个上下文管理器的引用,也可以是一句可执行的表达式,with 语句会自动执行这个表达式以获得上下文管理对象。with 语句的实际执行流程是这样的:

  1. 执行 context_exp 以获取上下文管理器
  2. 加载上下文管理器的 __exit__() 方法以备稍后调用
  3. 调用上下文管理器的 __enter__() 方法
  4. 如果有 as var 从句,则将 __enter__() 方法的返回值赋给 var
  5. 执行子代码块 with_suit
  6. 调用上下文管理器的 __exit__() 方法,如果 with_suit 的退出是由异常引发的,那么该异常的 type、value 和 traceback 会作为参数传给 __exit__(),否则传三个 None
  7. 如果 with_suit 的退出由异常引发,并且 __exit__() 的返回值等于 False,那么这个异常将被重新引发一次;如果 __exit__() 的返回值等于 True,那么这个异常就被无视掉,继续执行后面的代码

即,可以把 __exit__() 方法看成是“try / finally”的 finally,它总是会被自动调用。Python 里已经有了一些支持上下文管理协议的对象,比如文件对象,在使用 with 语句处理文件对象时,可以不再关心“打开的文件必须记得要关闭”这个问题了:

>>> with open('test.py') as f:
    print(f.readline())

    
#!/usr/bin/env python

>>> f.readline()
Traceback (most recent call last):
  File "<pyshell#3>", line 1, in <module>
    f.readline()
ValueError: I/O operation on closed file.

可以看到在 with 语句完成后,f 已经自动关闭了,这个过程就是在 f 的__exit__() 方法里完成的。然后下面再来详细介绍一下上下文管理器:

  • 内建类型:上下文管理器类型(Context Manager Types)

Python 使用上下文管理协议定义了一种运行时上下文环境(runtime context)。上下文管理协议由包含了一对方法的上下文管理对象实现:它会在子代码块运行前进入一个运行时上下文,并在代码块结束后退出该上下文。我们一般使用 with 语句来调用上下文管理对象,这样代码清晰度较好。

上下文管理器的两个方法:

contextmanager.__enter__()

  • 本方法进入运行时上下文环境,并返回自身或另一个与运行时上下文相关的对象。返回值会赋给 as 从句后面的变量,as 从句是可选的

  • 举一个返回自身的栗子比如文件对象。因为文件对象自身就是一个上下文管理器 ,所以“open()”语句返回的文件对象既被 with 获取,也会经由本方法传给 as。由下例可见,文件对象内就包含了上下文管理器的两个方法:

    with open('test.py') as f: 'enter' in dir(f) 'exit' in dir(f)

    True True

  • 返回一个相关对象的例子比如 decimal.localcontext() 所返回的对象。这个对象的 __enter__() 将 decimal 的主动上下文(active context)放到了一份原版 decimal 上下文的拷贝中并返回。这使得用户在改动 with 语句内的上下文环境时不会影响到语句外的部分:

    import decimal with decimal.localcontext() as ctx: ctx.prec = 22 print(decimal.getcontext().prec)

    22

    print(decimal.getcontext().prec) 28

  • 有一种 with 的用法是直接使用“with f=open('test.py'):”这样的语句,不用 as。这种做法在上面提到的 __enter__() 返回 self 时还好说,但如果返回的是另一个对象比如第二个例子的时候,就不好了。所以这里的话还是推荐一律使用 as 来赋值。

contextmanager.__exit__(exc_type,exc_val,exc_tb)

  • 本方法退出当前运行时上下文并返回一个布尔值,该布尔值标明了“如果 with_suit 的退出是由异常引发的,该异常是否须要被忽略”。如果 __exit__() 的返回值等于 False,那么这个异常将被重新引发一次;如果 __exit__() 的返回值等于 True,那么这个异常就被无视掉,继续执行后面的代码

  • 另外当 with_suit 的执行过程中抛出异常时,本方法会立即执行,并且异常的三个参数也会传进来。下面我们自定义一个上下文管理器,并可以通过一个参数设定是否要忽略异常:

    class Ctx(object): def init(self,ign=None): self.ign = ign def enter(self): pass def exit(self,exc_type,exc_val,exc_tb): return self.ign

    with Ctx(True): raise TypeError('Ignored?')

    with Ctx(False): raise TypeError('Ignored?')

    Traceback (most recent call last): File "<pyshell#28>", line 2, in raise TypeError('Ignored?') TypeError: Ignored?

  • 如果在本方法中又引发了新的异常,那么这个新异常将覆盖掉 with_suit 中的异常

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Python3:sqlalchemy对mysql数据库操作,非sql语句
Python3:sqlalchemy对mysql数据库操作,非sql语句python3authorlizmdatetime2018020110:00:00coding:utf8'''
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
3年前
JS 对象数组Array 根据对象object key的值排序sort,很风骚哦
有个js对象数组varary\{id:1,name:"b"},{id:2,name:"b"}\需求是根据name或者id的值来排序,这里有个风骚的函数函数定义:function keysrt(key,desc) {  return function(a,b){    return desc ? ~~(ak
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这