Python核心技术与实战——二一|巧用上下文管理器和with语句精简代码

我们在Python中对于with的语句应该是不陌生的,特别是在文件的输入输出操作中,那在具体的使用过程中,是有什么引伸的含义呢?与之密切相关的上下文管理器(context manager)又是什么呢?

什么是上下文管理器

在任何一种编程语言里,文件的输入输出、数据库的建立连接和断开等操作,都是很常见的资源管理操作。但是资源是有限的,在写程序的时候,我们必须保证这些资源在使用后得到释放,不然就容易造成资源泄漏,轻者系统处理缓慢,重则系统崩溃。

我们看一个例子:

for i in range(100000000):
    f = open(‘test.txt‘,‘w‘)
    f.write(‘hello‘)

我们在循环里打开了100000000个文件,但是在使用完毕后没有进行关闭操作,一运行代码,就报错了。

这就是一个典型的资源泄漏的案例,因为程序中同时打开了太多的文件,占用了太多的资源,造成崩溃。

为了解决这个问题,不同的编程语言都引入了不同的机制,在Python中,对应的解决方法就是上下文管理器(context manager)。上下文管理器能够自动分配资源并释放资源,其中最典型的应用就是with语句,所以上面的代码应该用这种方式来写

for x in range(100000000):
    with open(‘test.txt‘,‘w‘) as f:
        f.write(‘hello world‘)

这样,我们每次打开文件“test.txt"并写入字符以后,这个文件就会自动关闭,相应的资源也就得以释放,可以防止资源泄漏。当然,with的语句也可以用下面的方式来表示

f = open(‘test.txt‘,‘w‘)
try:
    f.write(‘hello world‘)
finally:
    f.close()

这里一定要注意finally的程序段,哪怕在写入的时候发生了异常,他也可以保证文件最终被关闭。不过于with想比较,就显得比较冗余了,并且还容易忽略finally,所以我们平时更倾向于使用with语句。

另外一种很典型的例子,就是Python中的线程锁(threrading.lock类),比如我们想要获得一个锁,执行相应的操作以后再将其释放,那么代码就应该是这样的

import threading
some_lock = threading.Lock()
some_lock.acquire()
try:
    pass
finally:
    some_lock.release()

而与其对应的with语句就非常简洁了

import threading
some_lock = threading.Lock()
with some_lock:
    pass

从上面两个例子可以发现,使用with语句,可以大大的简化代码结构,有效的避免资源泄漏的发生。



上下文管理器的实现

基于类的上下文管理器 

了接了上下文管理的概念和优点以后,我们就通过下面的例子,看看上下文管理器的原理,高清他的内部实现。我们在这里定义一个上下文管理类FileManager,来模拟Python的打开、关闭文件的操作

class FileManager():
    def __init__(self,name,mode):
        print(‘call __init__ method‘)
        self.name = name
        self.mode = mode
        self.file = None

    def __enter__(self):
        print(‘calling __enter__ method‘)
        self.file = open(self.name,self.mode)
        return self.file
    def __exit__(self,exc_type,exc_val,exc_tb):
        print(‘call __exit__ method‘)
        if self.file:
            self.file.close()

with FileManager(‘test.txt‘,‘w‘) as f:
    print(‘ready to write to file‘)
    f.write(‘hello world‘)

##########输出##########
call __init__ method
calling __enter__ method
ready to write to file
call __exit__ method

特别注意:当我们用类来创建上下文管理器的时候,必须保证这个类包括下面两个方法:

__enter__()
__exit__()

并且enter方法还要返回需要被管理的资源,方法exit里通常会存在一些释放、清理资源的操作,比如上面这段代码里的关闭文件等。

而当我们用with语句来执行上面这个上下文管理器的时候,会发生下面四个步骤:

1.构造方法__init__()会被调用,程序初始化对象FileManager,使得文件名和操作方式被传入。

2.方法__enter__()被调用,文件被以写入的模式打开,并且返回FileManager对戏那个赋值给变量f

3.字符串被写入文件

4.方法__exit__()被调用,关闭之前打开的文件流。

所以就有了上面列出的输出结果。

另外我们可以看到exit函数里传递了几个参数——exc_type.exc_val,exc_tb,分别表示exception_type,exception_value和traceback。当我们执行含有上下文管理器的with语句的时候,如果有异常抛出,异常的信息就会被包含在上面三个参数中,传给__exit__()函数。

因此,如果我们需要处理一些异常,可以在__exit__()函数中添加相应的代码

    def __exit__(self,exc_type,exc_val,exc_tb):
        print(‘call __exit__ method‘)
        if exc_type:
            print(f‘exc_type:{exc_type}‘)
            print(f‘exc_value:{exc_val}‘)
            print(f‘exc_traceback:{exc_tb}‘)
            print(‘exception handled‘)
            return True
        if self.file:
            self.file.close()

with FileManager(‘test.txt‘,‘w‘) as f:
    raise Exception(‘exception raised‘).with_traceback(None)

在修改了exit()方法以后我们在with语句中用raise手动抛出异常,我们可以看到代码有下面的输出

call __init__ method
calling __enter__ method
call __exit__ method
exc_type:<class ‘Exception‘>
exc_value:exception raised
exc_traceback:<traceback object at 0x000001AA82C8FF48>
exception handled

要注意的是,如果exit函数如果没有返回True,呢么异常仍然会被抛出。如果我们确定了异常已经被处理,那么在exit最后要加上True的返回值。



同样的,在数据库的连接等操作上,也常常使用上下文管理器来表示,下面是个简化的代码

class DBConnectionManager():
    def __init__(self,hostname,port):
        self.hostname = hostname
        self.port = port
        self.connection = None

    def  __enter__(self):
        self.connection = DBClient(self.hostname,self.port)
        return self

    def __exit__(self,exc_type,exc_val,exc_tb):
        self.connection.close()

with DBConnectionManager(‘localhost‘,‘8080‘) as db_client:
    pass

代码的具体含义和上面的例子类似,就不再详细说明了。只要我们写完了DBConnectionManager这个类以后,在每次建立数据库连接时,只要简单的利用with语句就可以了,并不要关系数据库的关闭、异常等,大大的提高了开发效率。

基于生成器的上下文管理器

上面那种基于类的上下文管理器在Python中利用非常广泛,我们在很多项目中都可以看得到,不过Python中的上下文管理器不仅仅局限于此,畜类基于类,它还可以基于生成器实现,我们看看下面的例子:

我们用一个装饰器contextlib.contextmanager来定义自己所需要的基于生成器的上下文管理器,用以支持with语句。同样我们用前面的FileManager来演示

from contextlib import contextmanager

@contextmanager
def file_manager(name,mode):
    try:
        f = open(name,mode)
        yield f
    finally:
        f.close()

with file_manager(‘test.txt‘,‘w‘) as f:
    f.write(‘hello world‘)

这段代码中,函数file_manager()是一个生成器,当我们执行with语句的时候,便会打开文件,并返回文件对象f,当with语句执行完毕以后,finally代码段中的关闭操作就会执行。

可以看到,使用基于生成器的上下文管理器的时候,我们不用再定义__enter__()和__exit__()两个函数,但是必须加上装饰器,这一点非常容易漏掉。

讲完这两种上下文管理器以后,我们要强调一点:不论是基于类的还是生成器的上下文管理器,两者在功能上是一样的,只不过有下面两点:

1.基于类的上下文管理器更加灵活,适用于大型的系统开发

2.基于生成器的上下文管理器更加方便、简洁,适用于中小型程序。

但是无论使用哪一种,我们一定要记得在exit函数或finally里写好释放资源的代码,这一点尤为重要。

总结

在这一章的开头我们通过一个简单的例子了解了资源泄漏的易发生的特性和其带来的后果,从而引入了上下文管理器这个概念:

  上下文管理器通常用在文件的IO操作和数据库的连接关闭等场景中,可以确保用过的资源得到迅速释放,有效提高了程序的安全性。

接着,我们通过自定义上下文管理器的示例,大致了解了上下文管理器工作的原理,并介绍基于类的上下文管理器和基于生成器的上下文管理器:两者功能相同,具体使用哪个要根据场景来选择。

另外,上下文管理器通常和with一起使用,大大提高了程序的简洁度,需要注意的是我们在使用with语句执行上下文操作的时候,一旦有异常抛出,异常的类型、值等拘役信息都会通过参数传递给__exit__()函数,我们可以自行定义相关的操作,而在对异常处理完毕以后,务必加上return True语句来保证程序的执行,否则仍然会抛出异常。

原文地址:https://www.cnblogs.com/yinsedeyinse/p/11992665.html

时间: 2024-11-10 12:06:28

Python核心技术与实战——二一|巧用上下文管理器和with语句精简代码的相关文章

python上下文管理器及with语句

with语句支持在一个叫上下文管理器的对象的控制下执行一系列语句,语法大概如下: with context as var: statements 其中的context必须是个上下文管理器,它实现了两个方法__enter__,__exit__. 1.需求是怎么产生的 在正常的管理各种系统资源(文件.锁定和连接),在涉及到异常时通常是个棘手的问题.异常很可能导致控制流跳过负责释放关键资源的语句. 看一段简单的文件写入代码: filename = 'my_file.txt' f = open(file

翻译《Writing Idiomatic Python》(五):类、上下文管理器、生成器

原书参考:http://www.jeffknupp.com/blog/2012/10/04/writing-idiomatic-python/ 上一篇:翻译<Writing Idiomatic Python>(四):字典.集合.元组 下一篇:TO BE UPDATED.. 2.7 类 2.7.1 用isinstance函数检查一个对象的类型 许多新手在接触Python之后会产生一种“Python中没有类型”的错觉.当然Python的对象是有类型的,并且还会发生类型错误.比如,对一个int型对象

Python上下文管理器与with语句

什么是上下文管理器 上下文管理器顾名思义是管理上下文的,也就是负责冲锋和垫后,而让主人专心完成自己的事情.我们在编写程序的时候,通常会将一系列操作放到一个语句块中,当某一条件为真时执行该语句快.有时候,我们需要再执行一个语句块时保持某种状态,并且在离开语句块后结束这种状态.例如对文件的操作,我们在打开一个文件进行读写操作时需要保持文件处于打开状态,而等操作完成之后要将文件关闭.所以,上下文管理器的任务是:代码块执行前准备,代码块执行后收拾.上下文管理器是在Python2.5加入的功能,它能够让你

Python核心技术与实战

课程目录:第00课.开篇词丨从工程的角度深入理解Python.rar第01课.如何逐步突破,成为Python高手?.rar第02课.Jupyter Notebook为什么是现代Python的必学技术?.rar第03课.列表和元组,到底用哪一个?.rar第04课.字典.集合,你真的了解吗?.rar第05课.深入浅出字符串.rar第06课.Python “黑箱”:输入与输出.rar第07课.修炼基本功:条件与循环.rar第08课.异常处理:如何提高程序的稳定性?.rar第09课.不可或缺的自定义函数

python上下文管理器ContextLib及with语句

http://blog.csdn.net/pipisorry/article/details/50444736 with语句 with语句是从 Python 2.5 开始引入的一种与异常处理相关的功能(2.5 版本中要通过 from __future__ import with_statement 导入后才可以使用),从 2.6 版本开始缺省可用(参考 What's new in Python 2.6? 中 with 语句相关部分介绍).with 语句适用于对资源进行访问的场合,确保不管使用过程

Python上下文管理器

上下文管理器 在使用Python编程中,可以会经常碰到这种情况:有一个特殊的语句块,在执行这个语句块之前需要先执行一些准备动作:当语句块执行完成后,需要继续执行一些收尾动作. 例如:当需要操作文件或数据库的时候,首先需要获取文件句柄或者数据库连接对象,当执行完相应的操作后,需要执行释放文件句柄或者关闭数据库连接的动作. 又如,当多线程程序需要访问临界资源的时候,线程首先需要获取互斥锁,当执行完成并准备退出临界区的时候,需要释放互斥锁. 对于这些情况,Python中提供了上下文管理器(Contex

【Python学习笔记】with语句与上下文管理器

with语句 上下文管理器 contextlib模块 参考引用 with语句 with语句时在Python2.6中出现的新语句.在Python2.6以前,要正确的处理涉及到异常的资源管理时,需要使用try/finally代码结构.如要实现文件在操作出现异常时也能正确关闭,则需要像如下实现: f = open("test.txt") try: for line in f.readlines(): print(line) finally: f.close() 不管文件操作有没有出现异常,t

浅谈Python中with(上下文管理器)的用法

例子一 首先来看一段代码: class Foo(object): def __init__(self): print('实例化一个对象') def __enter__(self): print('进入') def __exit__(self, exc_type, exc_val, exc_tb): print('退出') obj = Foo() with obj: print('正在执行') 上面代码执行结果为: 实例化一个对象 进入 正在执行 退出 结论1 我们知道,实例化Foo,得到obj对

Docker最全教程之Python爬网实战(二十一)

原文:Docker最全教程之Python爬网实战(二十一) Python目前是流行度增长最快的主流编程语言,也是第二大最受开发者喜爱的语言(参考Stack Overflow 2019开发者调查报告发布).笔者建议.NET.Java开发人员可以将Python发展为第二语言,一方面Python在某些领域确实非常犀利(爬虫.算法.人工智能等等),另一方面,相信我,Python上手完全没有门槛,你甚至无需购买任何书籍! 由于近期在筹备4.21的长沙开发者大会,耽误了不少时间.不过这次邀请到了腾讯资深技术