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

血红的双手。 提交于 2020-03-02 01:28:34

对象,是 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 <module>
    raise TypeError('Ignored?')
TypeError: Ignored?
    • 如果在本方法中又引发了新的异常,那么这个新异常将覆盖掉 with_suit 中的异常
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!