导读:pythoners都知道有个关键字叫"with",它可以实现使用某些"临时"声明的对象,而之后"什么也不用管",这个用法在python中叫上下文管理器。本文带你快速入门上下文管理器和with用法。

可以在一个with函数里写另一个with函数吗python python中with的用法_eclipse

01 初识

上下文管理器,英文context managers,在python官方文档中这样描述:

上下文管理器是一个对象,它定义了在执行 with 语句时要建立的运行时上下文。 上下文管理器处理进入和退出所需运行时上下文以执行代码块。 通常使用 with 语句(在 with 语句中描述),但是也可以通过直接调用它们的方法来使用。

那么可能还会有进一步的疑问:"上下文"又是什么呢?

可以在一个with函数里写另一个with函数吗python python中with的用法_eclipse_02

知乎某大佬关于"上下文"的解释

如果还是难懂,那就举个例子。在python中,写入文件的基本操作可能是这样的:

1f = open('a.txt', 'w')
2f.write('22')
3f.close()

如果考虑在文件操作期间可能会触发异常、造成文件不能关闭,那么比较安全的改进写法是:

1f = open('a.txt', 'w')
2try:
3    f.write('22')
4except:
5    print('File eroor!')
6finally:
7    f.close()

当使用with包装上下文管理器后,那么只需要这样:

1with open('b.txt', 'w') as f:
2    f.write('22')

可能会有这样疑问,不就是节省了一行文件关闭的代码而已嘛,也没看出有多高效啊!

这里先抛出结论:使用with管理上下文不仅可以在执行with语句体后自动执行退出操作(即__exit__方法定义语句),更重要的是能够在发生异常时,确保始终能执行退出操作、释放相应的资源。

02 详解

在PEP343(该PEP文档由python之父龟叔发起,最早在python2.5版本引入。如想了解更多关于PEP知识,可阅读PEP入门指南),对上下文管理器给出如下定义:

上下文管理器是指提供了一对专门方法__enter __()和__exit __(),这些方法在with语句的主体进入和退出时被调用。在Python语言中添加了一个“ with”新语句,从而可以排除try / finally语句的标准用法。

也就是说,如果某个类定义了__enter__、__exit__方法,允许在语句体执行前进入该上下文、执行后退出该上下文,那么这样的类就可以称作是上下文管理器对象。而且该用法可用作对try/finally的替代,以处理可能存在的异常。

前面举了文件操作的with用法,说明文件操作的对象是一个上下文管理器对象,那么我们先来看下文件操作类来怎样定义的:

1##_pyio.py
 2### Context manager ###
 3def __enter__(self):  # That's a forward reference
 4    """Context management protocol.  Returns self (an instance of IOBase)."""
 5    self._checkClosed()
 6    return self
 7
 8def __exit__(self, *args):
 9    """Context management protocol.  Calls close()"""
10    self.close()

注:open()函数返回一个FileIO类对象,FileIO类继承自RawIOBase类,RawIOBase又继承自IOBase类,而IOBase类定义了__enter__和__exit__方法,因而是一个上下文管理器对象。

同时,在IOBase类说明文档中,也给出了这样介绍:

1"""IOBase also supports the :keyword:`with` statement. In this example,
2fp is closed after the suite of the with statement is complete:
3
4with open('spam.txt', 'r') as fp:
5    fp.write('Spam and eggs!')"""

再举个例子,在python并发之concurrent快速入门一文中,对比多线程和多进程的并发操作时,也使用了with包装上下文管理器的用法:

1from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
2def multi_thread():
3    with ThreadPoolExecutor() as executor:
4        return list(executor.map(is_prime, PRIMES))
5
6def multi_process():
7    with ProcessPoolExecutor() as executor:
8        return list(executor.map(is_prime, PRIMES))

那么有理由相信,concurrent.futures类肯定也定义了__enter__和__exit__方法,具体如下:

1##_base.py
2def __enter__(self):
3    return self
4
5def __exit__(self, exc_type, exc_val, exc_tb):
6    self.shutdown(wait=True)
7    return False

注:ThreadPoolExecutor和ProcessPoolExecutor两个类均继承自Executor类,而Excutor类是一个上下文管理器对象。

需补充说明的是,正如上述示例代码给出的那样,__exit__()方法默认接收3个参数:exc_type、exc_val、exc_tb,如果with语句体发生异常,则3个参数分别赋值为type(error)、str(error)、error.__traceback__,否则均为None。

至此,我们可以得出这样的结论:一个上下文管理器对象是指定义了__enter__和__exit__方法的类对象;反之,定义了__enter__和__exit__方法的类可以应用with包装实现上下文管理器用法。

应用with包装上下文管理器时:执行with语句生成一个实例对象时,自动调用__enter__方法创建一个适用于上下文环境的类对象,完毕后无论是否触发异常都会调用__exit__方法执行退出操作。

03 实战

实际上,上下文管理器常适用于带有资源管理的操作,如前面例子中给出的文件操作和并发操作。这里,我们举一个操作数据库的例子,实现一个简单的打开数据库类。

1import pymysql
 2class OpenMySQL(object):
 3    def __init__(self, db):
 4        self.conn = pymysql.connect(host="localhost", user="root", password="123456", db=db, charset='utf8')
 5
 6    def __enter__(self):
 7        return self.conn.cursor()
 8
 9    def __exit__(self, exc_type, exc_val, exc_tb):
10        self.conn.commit()
11        self.conn.close()
12
13if __name__ == '__main__':
14    with OpenMySQL('mytest') as sql:
15        sql_insert = 'insert into tbtest values (2);'
16        sql.execute(sql_insert)

代码功能执行正常,且相比于基本的数据库connect()--commit()--close()操作流程来说,要优雅许多。

注:写完例子后查阅源码发现pymysql.connect方法返回对象本身就是一个上下文管理器对象……。但仅仅用于举例的话,这也足以说明with包装上下文管理器的用法了。

04 总结

本文对python中上下文管理器和with用法进行了简单介绍,包括:

上下文管理器是一个实现了__enter__、__exit__魔法方法的类对象

定义了__enter__、__exit__方法的类对象即可用作上下文管理器

  • 上下文管理器通常使用with语句实现

with语句可实现简洁版的try/finally替代用法

可以在一个with函数里写另一个with函数吗python python中with的用法_java_03