深入学习Python中的上下文管理器与else块

前言

本文主要个大家介绍了关于Python上下文管理器与else块的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。

在开始之前,我们先来看看下面这段话:

最终,上下文管理器可能几乎与子程序(subroutine)本身一样重要。目前,我们只了解了上下文管理器的皮毛……Basic 语言有with 语句,而且很多语言都有。但是,在各种语言中 with 语句的作用不同,而且做的都是简单的事,虽然可以避免不断使用点号查找属性,但是不会做事前准备和事后清理。不要觉得名字一样,就意味着作用也一样。with 语句是非常了不起的特性。

——Raymond Hettinger

雄辩的 Python 布道者

先做这个,再做那个:if语句之外的else块

这个语言特性不是什么秘密,但却没有得到重视:else 子句不仅能在if 语句中使用,还能在 for、while 和 try 语句中使用。for/else、while/else 和 try/else 的语义关系紧密,不过与if/else 差别很大。起初,else 这个单词的意思阻碍了我对这些特性的理解,但是最终我习惯了。

else 子句的行为如下:

for

  仅当 for 循环运行完毕时(即 for 循环没有被 break 语句中止)才运行 else 块。

while

  仅当 while 循环因为条件为假值而退出时(即 while 循环没有被break 语句中止)才运行 else 块。

try

  仅当 try 块中没有异常抛出时才运行 else 块。官方文档(https://docs.python.org/3/reference/compound_stmts.html)还指出:“else 子句抛出的异常不会由前面的 except 子句处理。”

注意:

  在所有情况下,如果异常或者 return、break 或 continue 语句导致控制权跳到了复合语句的主块之外,else 子句也会被跳过。

  在这些语句中使用 else 子句通常能让代码更易于阅读,而且能省去一些麻烦,不用设置控制标志或者添加额外的 if 语句。

在循环中使用 else 子句的方式如下述代码片段所示:

 for item in my_list:
  if item.flavor == 'banana':
   break
  else:
   raise ValueError('No banana flavor found!')

一开始,你可能觉得没必要在 try/except 块中使用 else 子句。毕竟,在下述代码片段中,只有 dangerous_call() 不抛出异常,after_call() 才会执行,对吧?

 try:
  dangerous_call()
  after_call()
 except OSError:
  log('OSError...')

然而,after_call() 不应该放在 try 块中。为了清晰和准确,try 块中应该只抛出预期异常的语句。因此,像下面这样写更好:

 try:
  dangerous_call()
 except OSError:
  log('OSError...')
 else:
  after_call()

现在很明确,try 块防守的是 dangerous_call() 可能出现的错误,而不是 after_call() 。而且很明显,只有 try 块不抛出异常,才会执行after_call()

上下文管理器和with块

  上下文管理器对象存在的目的是管理 with 语句,就像迭代器的存在是为了管理 for 语句一样。

  with 语句的目的是简化 try/finally 模式。这种模式用于保证一段代码运行完毕后执行某项操作,即便那段代码由于异常、return 语句或sys.exit() 调用而中止,也会执行指定的操作。finally 子句中的代码通常用于释放重要的资源,或者还原临时变更的状态。

  上下文管理器协议包含 __enter__ 和 __exit__ 两个方法。with 语句开始运行时,会在上下文管理器对象上调用 __enter__ 方法。with 语句运行结束后,会在上下文管理器对象上调用 __exit__ 方法,以此扮演 finally 子句的角色。

🌰 演示把文件对象当成上下文管理器使用

>>> with open('mirror.py') as fp: # fp绑定到打开的文件上,因为文件的__enter__方法返回self
...   src = fp.read(60) # 从fp中读取一些数据
...
>>> len(src)
>>> fp # fp变量依然可以使用
<_io.TextIOWrapper name='mirror.py' mode='r' encoding='UTF-8'>
>>> fp.closed, fp.encoding # 可以读取fp对象的属性
(True, 'UTF-8')
>>> fp.read(60) # 但是不能在fp上执行I/O操作,因为在with块的结尾,调用了TextIOWrappper.__exit__方法把文件关闭了
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file.

测试 LookingGlass 上下文管理器类

>>> from mirror import LookingGlass
>>> with LookingGlass() as what: # 上下文管理器是LookingGlass类的实例;Python在上下文管理器上调用__enter__方法,把返回结果绑定在what上
...  print('Alice, Kitty and Snowdrop') # 打印一个字符串,然后打印what变量的值
...  print(what)
...
pordwonS dna yttiK ,ecilA # 打印出的内容是反向的
YKCOWREBBAJ
>>> what # 现在,with块已经执行完毕,可以看出,__enter__方法返回的值,即存储在what变量中的值是字符串'JABBERWOCKY'
'JABBERWOCKY'
>>> print('Back to normal.') # 输出不在是反向的了
Back to normal.

mirror.py:LookingGlass 上下文管理器类的代码

class LookingGlass:

 def __enter__(self):      # 除了 self 之外,Python调用__enter__方法时不窜入其他参数
  import sys
  self.original_write = sys.stdout.write # 把原来的 sys.stdout.write 方法保存在一个实例属性中,供后面使用
  sys.stdout.write = self.reverse_write # 为 sys.stdout.write 打猴子补丁,替换成自己编写的方法
  return 'JABBERWOCKY'     # 返回 'JABBERWOCKY' 字符串,这样才有内容存入目标变量 what

 def reverse_write(self, text):    # 这是用于取代 sys.stdout.write 的方法,把 text 参数的内容反转,然后调用原来的方法实现
  return self.original_write(text[::-1])

 def __exit__(self, exc_type, exc_val, traceback): # 如果一切正常,Python会调用__exit__方法传入的参数是三个None,如果抛出异常,则三个参数是异常的数据
  import sys
  sys.stdout.write = self.original_write # 还原成原来的sys.studout.write方法
  if exc_type is ZeroDivisionError:  # 如果有异常,而且是 ZeroDivisionError 类型,打印一个消息
   print('Please DO NOT divide by zero!')
   return True       # 然后返回 True,告诉解释器,异常已经处理

解释器调用 __enter__ 方法时,除了隐式的 self 之外,不会传入任何参数。传给 __exit__ 方法的三个参数列举如下。

exc_type

  异常类(例如 ZeroDivisionError)

exc_value

  异常实例。有时会有参数传给异常构造方法,例如错误消息,这些参数可以使用 exc_value.args 获取

traceback

  traceback 对象

在 with 块之外使用 LookingGlass 类

>>> from mirror import LookingGlass
>>> manager = LookingGlass() # 实例化并审查manager实例,等同于 with LookingGlass() as manager
>>> manager
<mirror.LookingGlass object at 0x2a578ac>
>>> monster = manager.__enter__() # 在上下文管理器中调用__enter__()方法,把结果存储在monster中
>>> monster == 'JABBERWOCKY' # monster的值是字符串'JABBERWOCKY',打印出来的True标识符是反向,因为用了猴子补丁
eurT
>>> monster
'YKCOWREBBAJ'
>>> manager
>ca875a2x0 ta tcejbo ssalGgnikooL.rorrim<
>>> manager.__exit__(None, None, None) # 调用manager.__exit__,还原成之前的stdout.write
>>> monster
'JABBERWOCKY'

contextlib模块中的实用工具

closing

  如果对象提供了 close() 方法,但没有实现__enter__/__exit__ 协议,那么可以使用这个函数构建上下文管理器。

suppress

  构建临时忽略指定异常的上下文管理器。

@contextmanager

  这个装饰器把简单的生成器函数变成上下文管理器,这样就不用创建类去实现管理器协议了。

ContextDecorator

  这是个基类,用于定义基于类的上下文管理器。这种上下文管理器也能用于装饰函数,在受管理的上下文中运行整个函数。

ExitStack

  这个上下文管理器能进入多个上下文管理器。with 块结束时,ExitStack 按照后进先出的顺序调用栈中各个上下文管理器的__exit__ 方法。如果事先不知道 with 块要进入多少个上下文管理器,可以使用这个类。例如,同时打开任意一个文件列表中的所有文件。

使用@contextmanager

  @contextmanager 装饰器能减少创建上下文管理器的样板代码量,因为不用编写一个完整的类,定义 __enter__ 和 __exit__ 方法,而只需实现有一个 yield 语句的生成器,生成想让 __enter__ 方法返回的值。

  在使用 @contextmanager 装饰的生成器中,yield 语句的作用是把函数的定义体分成两部分:yield 语句前面的所有代码在 with 块开始时(即解释器调用 __enter__ 方法时)执行, yield 语句后面的代码在with 块结束时(即调用 __exit__ 方法时)执行。

mirror_gen.py:使用生成器实现的上下文管理器

import contextlib

@contextlib.contextmanager    # 应用 contextmanager 装饰器
def looking_glass():
 import sys
 original_write = sys.stdout.write # 贮存原来的 sys.stdout.write 方法

 def reverse_write(text):   # 定义自定义的 reverse_write 函数;在闭包中可以访问 original_write
  original_write(text[::-1])

 sys.stdout.write = reverse_write # 把 sys.stdout.write 替换成 reverse_write
 yield 'JABBERWOCKY'     # 产出一个值,这个值会绑定到 with 语句中 as 子句的目标变量上
 sys.stdout.write = original_write # 控制权一旦跳出 with 块,继续执行 yield 语句之后的代码;这里是恢复成原来的 sys. stdout.write 方法

with looking_glass() as what:   # 直接通过上下文管理器实现with的功能
 print('Alice, Kitty and Snowdrop')
 print(what)

print(what)

以上代码执行的结果为:

pordwonS dna yttiK ,ecilA
YKCOWREBBAJ
JABBERWOCKY

其实,contextlib.contextmanager 装饰器会把函数包装成实现__enter__ 和 __exit__ 方法的类

这个类的 __enter__ 方法有如下作用:

  (1) 调用生成器函数,保存生成器对象(这里把它称为 gen)。

  (2) 调用 next(gen),执行到 yield 关键字所在的位置。

  (3) 返回 next(gen) 产出的值,以便把产出的值绑定到 with/as 语句中的目标变量上。

with 块终止时,__exit__ 方法会做以下几件事:

  (1) 检查有没有把异常传给 exc_type;如果有,调用gen.throw(exception) , 在生成器函数定义体中包含 yield 关键字的那一行抛出异常。

  (2) 否则,调用 next(gen) ,继续执行生成器函数定义体中 yield 语句之后的代码。

注意:  

  上面的 🌰 有一个严重的错误:如果在 with 块中抛出了异常,Python 解释器会将其捕获,然后在 looking_glass 函数的 yield 表达式里再次抛出。但是,那里没有处理错误的代码,因此 looking_glass 函数会中止,永远无法恢复成原来的 sys.stdout.write 方法,导致系统处于无效状态。

mirror_gen_exc.py:基于生成器的上下文管理器,而且实现了异常处理

import contextlib

@contextlib.contextmanager
def looking_glass():
 import sys
 original_write = sys.stdout.write

 def reverse_write(text):
  original_write(text[::-1])

 sys.stdout.write = reverse_write
 msg = ''        #创建一个变量,用于保存可能出现的错误消息;
 try:
  yield 'JABBERWOCKY'
 except ZeroDivisionError:    #处理 ZeroDivisionError 异常,设置一个错误消息
  msg = 'Please DO NOT divide by zero!'
 finally:
  sys.stdout.write = original_write # 撤销对 sys.stdout.write 方法所做的猴子补丁
  if msg:
   print(msg)      # 如果设置了错误消息,把它打印出来

注意:

  使用 @contextmanager 装饰器时,要把 yield 语句放在try/finally 语句中(或者放在 with 语句中),这是无法避免的,因为我们永远不知道上下文管理器的用户会在 with 块中做什么。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • python 上下文管理器使用方法小结

    上下文管理器最常用的是确保正确关闭文件, with open('/path/to/file', 'r') as f: f.read() with 语句的基本语法, with expression [as variable]: with-block expression是一个上下文管理器,其实现了enter和exit两个函数.当我们调用一个with语句时, 依次执行一下步骤, 1.首先生成一个上下文管理器expression, 比如open('xx.txt'). 2.执行expression.en

  • Python中的with语句与上下文管理器学习总结

    0.关于上下文管理器 上下文管理器是可以在with语句中使用,拥有__enter__和__exit__方法的对象. with manager as var: do_something(var) 相当于以下情况的简化: var = manager.__enter__() try: do_something(var) finally: manager.__exit__() 换言之,PEP 343中定义的上下文管理器协议允许将无聊的try...except...finally结构抽象到一个单独的类中,

  • 遗传算法之Python实现代码

    写在前面 之前的文章中已经讲过了遗传算法的基本流程,并且用MATLAB实现过一遍了.这一篇文章主要面对的人群是看过了我之前的文章,因此我就不再赘述遗传算法是什么以及基本的内容了,假设大家已经知道我是怎么写遗传算法的了. Python的遗传算法主函数 我的思想是,创建一个染色体的类,其中包括了两个变量:染色体chrom与适应度fitness.因此我们就可以通过直接建立对象来作为种群中的个体. #染色体的类 class Chrom: chrom = [] fitness = 0 def showCh

  • Python上下文管理器和with块详解

    上下文管理器和with块,具体内容如下 上下文管理器对象存在的目的是管理 with 语句,就像迭代器的存在是为了管理 for 语句一样. with 语句的目的是简化 try/finally 模式.这种模式用于保证一段代码运行完毕后执行某项操作,即便那段代码由于异常. return 语句或 sys.exit() 调用而中止,也会执行指定的操作. finally 子句中的代码通常用于释放重要的资源,或者还原临时变更的状态. ==上下文管理器协议包含enter和exit两个方法==. with 语句开

  • 正确理解python中的关键字“with”与上下文管理器

    前言 如果你有阅读源码的习惯,可能会看到一些优秀的代码经常出现带有 "with" 关键字的语句,它通常用在什么场景呢?今天就来说说 with 和 上下文管理器. 对于系统资源如文件.数据库连接.socket 而言,应用程序打开这些资源并执行完业务逻辑之后,必须做的一件事就是要关闭(断开)该资源. 比如 Python 程序打开一个文件,往文件中写内容,写完之后,就要关闭该文件,否则会出现什么情况呢?极端情况下会出现 "Too many open files" 的错误,

  • Python深入学习之上下文管理器

    上下文管理器(context manager)是Python2.5开始支持的一种语法,用于规定某个对象的使用范围.一旦进入或者离开该使用范围,会有特殊操作被调用 (比如为对象分配或者释放内存).它的语法形式是with...as... 关闭文件 我们会进行这样的操作:打开文件,读写,关闭文件.程序员经常会忘记关闭文件.上下文管理器可以在不需要文件的时候,自动关闭文件. 下面我们看一下两段程序: 复制代码 代码如下: # without context manager f = open("new.t

  • 深入解析Python中的上下文管理器

    1. 上下文管理器是什么? 举个例子,你在写Python代码的时候经常将一系列操作放在一个语句块中: (1)当某条件为真 – 执行这个语句块 (2)当某条件为真 – 循环执行这个语句块 有时候我们需要在当程序在语句块中运行时保持某种状态,并且在离开语句块后结束这种状态. 所以,事实上上下文管理器的任务是 – 代码块执行前准备,代码块执行后收拾. 上下文管理器是在Python2.5加入的功能,它能够让你的代码可读性更强并且错误更少.接下来,让我们来看看该如何使用. 2. 如何使用上下文管理器? 看

  • 深入学习Python中的上下文管理器与else块

    前言 本文主要个大家介绍了关于Python上下文管理器与else块的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 在开始之前,我们先来看看下面这段话: 最终,上下文管理器可能几乎与子程序(subroutine)本身一样重要.目前,我们只了解了上下文管理器的皮毛--Basic 语言有with 语句,而且很多语言都有.但是,在各种语言中 with 语句的作用不同,而且做的都是简单的事,虽然可以避免不断使用点号查找属性,但是不会做事前准备和事后清理.不要觉得名字一样,就意

  • Python中的上下文管理器相关知识详解

    前言 with 这个关键字,对于每一学习Python的人,都不会陌生. 操作文本对象的时候,几乎所有的人都会让我们要用 with open ,这就是一个上下文管理的例子.你一定已经相当熟悉了,我就不再废话了. with open('test.txt') as f: print f.readlines() 什么是上下文管理器? 基本语法 with EXPR as VAR: BLOCK 先理清几个概念 1. 上下文表达式:with open('test.txt') as f: 2. 上下文管理器:o

  • Python中的上下文管理器和with语句的使用

    Python2.5之后引入了上下文管理器(context manager),算是Python的黑魔法之一,它用于规定某个对象的使用范围.本文是针对于该功能的思考总结. 为什么需要上下文管理器? 首先,需要思索下为什么需要引入上下文管理器. 在正常情况下,管理各种系统资源(如文件).数据库连接时,通常是先打开这些资源,执行完相应的业务逻辑,最后关闭资源. 举两个例子: 1.使用Python打开一个文件写入内容,之后需要关闭这个文件.如果不正常关闭的话可能会在文件操作时出现异常,因为系统允许你打开的

  • Python with语句上下文管理器两种实现方法分析

    本文实例讲述了Python with语句上下文管理器.分享给大家供大家参考,具体如下: 在编程中会经常碰到这种情况:有一个特殊的语句块,在执行这个语句块之前需要先执行一些准备动作:当语句块执行完成后,需要继续执行一些收尾动作.例如,文件读写后需要关闭,数据库读写完毕需要关闭连接,资源的加锁和解锁等情况. 对于这种情况python提供了上下文管理器(Context Manager)的概念,可以通过上下文管理器来定义/控制代码块执行前的准备动作,以及执行后的收尾动作. 一.为何使用上下文管理器 1.

  • Python中with上下文管理协议的作用及用法

    目录 1.简介 2.try… except语句 (1)try…except的标准格式 (2)程序执行流程 (3)异常分类 3.try…finallly语句 4.with…as语句 (1)With语句的基本语法 (2)with语句原理 1.简介 with是从Python2.5引入的一个新的语法,它是一种上下文管理协议,目的在于从流程图中把 try,except 和finally 关键字和资源分配释放相关代码统统去掉,简化try…except…finlally的处理流程. 那我们先理解一下try…e

  • Python编程ContextManager上下文管理器讲解

    目录 什么是上下文管理器 官方解释 简单一句话 __enter__(self) __exit__(self, exc_type, exc_value, exc_traceback) 有哪些常见上下文管理器? 打开文件 拆分了解 执行顺序 自定义上下文管理器 基于类实现上下文管理器 总结 基于生成器实现上下文管理器 总结 with 语句的教程 什么是上下文管理器 官方解释 上下文管理器是一个对象它定义了在执行 with 语句时要建立的运行时上下文上下文管理器处理进入和退出所需的运行时上下文以执行代

  • 详解Python中contextlib上下文管理模块的用法

    咱们用的os模块,读取文件的时候,其实他是含有__enter__ __exit__ .  一个是with触发的时候,一个是退出的时候. with file('nima,'r') as f: print f.readline() 那咱们自己再实现一个标准的可以with的类. 我个人写python的时候,喜欢针对一些需要有关闭逻辑的代码,构造成with的模式 . #encoding:utf-8 class echo: def __enter__(self): print 'enter' def __

  • 详解利用上下文管理器扩展Python计时器

    目录 一个 Python 定时器上下文管理器 了解 Python 中的上下文管理器 理解并使用 contextlib 创建 Python 计时器上下文管理器 使用 Python 定时器上下文管理器 写在最后 上文中,我们一起学习了手把手教你实现一个 Python 计时器.本文中,云朵君将和大家一起了解什么是上下文管理器 和 Python 的 with 语句,以及如何完成自定义.然后扩展 Timer 以便它也可以用作上下文管理器.最后,使用 Timer 作为上下文管理器如何简化我们自己的代码. 上

随机推荐