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

前言

如果你有阅读源码的习惯,可能会看到一些优秀的代码经常出现带有 “with” 关键字的语句,它通常用在什么场景呢?今天就来说说 with 和 上下文管理器。

对于系统资源如文件、数据库连接、socket 而言,应用程序打开这些资源并执行完业务逻辑之后,必须做的一件事就是要关闭(断开)该资源。

比如 Python 程序打开一个文件,往文件中写内容,写完之后,就要关闭该文件,否则会出现什么情况呢?极端情况下会出现 "Too many open files" 的错误,因为系统允许你打开的最大文件数量是有限的。

同样,对于数据库,如果连接数过多而没有及时关闭的话,就可能会出现 "Can not connect to MySQL server Too many connections",因为数据库连接是一种非常昂贵的资源,不可能无限制的被创建。

来看看如何正确关闭一个文件。

普通版:

def m1():
 f = open("output.txt", "w")
 f.write("python之禅")
 f.close()

这样写有一个潜在的问题,如果在调用 write 的过程中,出现了异常进而导致后续代码无法继续执行,close 方法无法被正常调用,因此资源就会一直被该程序占用者释放。那么该如何改进代码呢?

进阶版:

def m2():
 f = open("output.txt", "w")
 try:
 f.write("python之禅")
 except IOError:
 print("oops error")
 finally:
 f.close()

改良版本的程序是对可能发生异常的代码处进行 try 捕获,使用 try/finally 语句,该语句表示如果在 try 代码块中程序出现了异常,后续代码就不再执行,而直接跳转到 except 代码块。而无论如何,finally 块的代码最终都会被执行。因此,只要把 close 放在 finally 代码中,文件就一定会关闭。

高级版:

def m3():
 with open("output.txt", "w") as f:
 f.write("Python之禅")

一种更加简洁、优雅的方式就是用 with 关键字。open 方法的返回值赋值给变量 f,当离开 with 代码块的时候,系统会自动调用 f.close() 方法, with 的作用和使用 try/finally 语句是一样的。那么它的实现原理是什么?

在讲 with 的原理前要涉及到另外一个概念,就是上下文管理器(Context Manager)。

上下文管理器

任何实现了 __enter__() __exit__() 方法的对象都可称之为上下文管理器,上下文管理器对象可以使用 with 关键字。显然,文件(file)对象也实现了上下文管理器。

那么文件对象是如何实现这两个方法的呢?我们可以模拟实现一个自己的文件类,让该类实现 __enter__() __exit__() 方法。

class File():

 def __init__(self, filename, mode):
 self.filename = filename
 self.mode = mode

 def __enter__(self):
 print("entering")
 self.f = open(self.filename, self.mode)
 return self.f

 def __exit__(self, *args):
 print("will exit")
 self.f.close()

__enter__() 方法返回资源对象,这里就是你将要打开的那个文件对象, __exit__() 方法处理一些清除工作。

因为 File 类实现了上下文管理器,现在就可以使用 with 语句了。

with File('out.txt', 'w') as f:
 print("writing")
 f.write('hello, python')

这样,你就无需显示地调用 close 方法了,由系统自动去调用,哪怕中间遇到异常 close 方法也会被调用。

contextlib

Python 还提供了一个 contextmanager 的装饰器,更进一步简化了上下文管理器的实现方式。通过 yield 将函数分割成两部分,yield 之前的语句在 __enter__ 方法中执行,yield 之后的语句在 __exit__ 方法中执行。紧跟在 yield 后面的值是函数的返回值。

from contextlib import contextmanager

@contextmanager
def my_open(path, mode):
 f = open(path, mode)
 yield f
 f.close()

调用

with my_open('out.txt', 'w') as f:
 f.write("hello , the simplest context manager")

总结

Python 提供了 with 语法用于简化资源操作的后续清除操作,是 try/finally 的替代方法,实现原理建立在上下文管理器之上。此外,Python 还提供了一个 contextmanager 装饰器,更进一步简化上下管理器的实现方式。以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

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

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

  • 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中的上下文管理器

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

  • 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上下文管理器和with块详解

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

  • 遗传算法之Python实现代码

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

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

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

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

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

  • 正确理解Python中if __name__ == '__main__'

    在Python,我们经常会编写 if __name__ == '__main__' 这么一段代码,这段代码该怎么来理解? 这段代码的功能理解如下: 一个python的文件有两种使用的方法: 作用一,直接作为脚本执行. 作用二,import到其他的python脚本中被调用(模块重用)执行. if __name__ == '__main__': 的作用就是控制这两种情况执行代码的过程,在if __name__ == '__main__': 下的代码只有在第一种情况下(即文件作为脚本直接执行)才会被执

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

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

  • Python上下文管理器Content Manager

    在 Python 中,我们会经常听到上下文管理器(Context Manager),那我们探讨下这是什么,又有什么功能. 在 Python 中的上下文管理器中,使用 with 打开文件是使用最多的,其中离开 with 包含的语句后会执行一些类似于清理的工作,如关闭文件,关闭连接对象等操作. 实践 我们在代码实践的时候,忽略了在同一代码片段中,先打开文件,然后直接对文件进行其他处理,因为这样没有任何意义,资源是处于被占用的情况. 先看下面检测的代码: #!/usr/bin/env python #

  • 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中的浅拷贝和深拷贝

    在讲什么是深浅拷贝之前,我们先来看这样一个现象: a = ['scolia', 123, [], ] b = a[:] b[2].append(666) print a print b 为什么我只对b进行修改,却影响到了a呢?看过我在之前的文章中就说过:序列中保存的都是内存的引用. 所以,当我们通过b去修改里面的空列表的时候,其实就是修改内存中的同一个对象,所以会影响到a. a = ['scolia', 123, [], ] b = a[:] print id(a), id(a[0]), id(

随机推荐