with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的清理操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。

1.术语

要使用 with 语句,首先要明白上下文管理器这一概念。有了上下文管理器,with 语句才能工作。

上下文管理协议(Context Management Protocol): 包含方法 __enter__()__exit__(),支持该协议的对象要实现这两个方法。

上下文管理器(Context Manager):支持上下文管理协议的对象,这种对象实现了__enter__()__exit__() 方法。上下文管理器定义执行 with 语句时要建立的运行时上下文,负责执行 with 语句块上下文中的进入与退出操作。通常使用 with 语句调用上下文管理器,也可以通过直接调用其方法来使用。

运行时上下文(runtime context):由上下文管理器创建,通过上下文管理器的 __enter__()__exit__()方法实现,__enter__() 方法在语句体执行之前进入运行时上下文,__exit__() 在语句体执行完后从运行时上下文退出。with 语句支持运行时上下文这一概念。

上下文表达式(Context Expression):with 语句中跟在关键字 with 之后的表达式,该表达式要返回一个上下文管理器对象。

语句体(with-body):with 语句包裹起来的代码块,在执行语句体之前会调用上下文管理器的 __enter__() 方法,执行完语句体之后会执行 __exit__() 方法。

2.with语法格式

with context_expression [as target(s)]:
    pass

3.自定义上下文管理器

要实现自定义上下文管理器必须要实现__enter__()__exit__()两个方法。

class MyResource:

    def __enter__(self):
        print('enter')
    
    def __exit__(self,exc_type, exc_value, exc_tb):
        print('exit')

我们完成了一个名为MyResource的上下文管理器,后面我们详细介绍上下文管理器。__exit__()方法必须接收4个参数

4.with执行顺序

with语句运行上下文管理器,先执行__enter__()方法然后执行with-body里面的代码,最后执行__exit__()方法。我们接下来验证一下

class MyResource:

    def __enter__(self):
        print('enter')
    
    def __exit__(self,exc_type, exc_value, exc_tb):
        print('exit')

with MyResource():
    print("with-body")

#输出
"enter"
"with-body"
"exit"

5.with的as语句

如果指定了as子句的话,会将上下文管理器的 __enter__() 方法的返回值赋值给 target(s)。target(s) 可以是单个变量或多个变量

class MyResource:

    def __enter__(self):        
        print('enter')
        a = 1
        return a
    
    def __exit__(self,exc_type, exc_value, exc_tb):
        print('exit')

with MyResource() as resource:
    print(resource)

#输出
"enter"
1
"exit"

__enter__中可以返回多个变量

class MyResource:

    def __enter__(self):        
        print('enter')
        return ('张三',12,'男')
    
    def __exit__(self,exc_type, exc_value, exc_tb):
        print('exit')

with MyResource() as resource:
    print(resource)

# 输出
enter
('张三', 12, '男')
exit

6.exit

在上面例子中,大家看到__exit__方法参数必须是4个,这是为什么呢?当程序在with-body中抛出异常,可以在__exit__中处理异常。

class MyResource:

    def __enter__(self):        
        print('enter')
        return self
    
    def __exit__(self,exc_type, exc_value, exc_tb):
        print("exc_type:",exc_type)
        print("exc_value:",exc_value)
        print("exc_tb:", exc_tb) 

with MyResource() as resource:
    1/0  #会抛出异常
    print('with-body')

#输出
enter
exc_type: <class 'ZeroDivisionError'>
exc_value: division by zero
exc_tb: <traceback object at 0x02AD21E8>
Traceback (most recent call last):
  File "re/re1.py", line 13, in <module>
    1/0
ZeroDivisionError: division by zero

从上面代码的数据结果来看,不管with-body语句是否抛出异常,最后都会执行__exit__方法,如果with-body语句没有异常,那么exc_tpey,exc_value,exc_tb的值都是none,with-body有异常,那么exc_tpye(异常类型),exc_value(异常的值),exc_tb(异常信息)

exit 返回值

__exit__方法中也是有返回值的,但是返回值只有两种TrueFalse。在上面中我们没有__exit__方法中写return也没有报错,这是因为如果不写,方法会默认返回None,python中None对应boolean的值就是False

接下来我们继续探讨下,返回TrueFalse有什么区别?

exit 返回Fasle

class MyResource:

    def __enter__(self):        
        print('enter')
        return self
    
    def __exit__(self,exc_type, exc_value, exc_tb):
        print('exit')
        return False

try:
    with MyResource() as resource:
        1/0  #会抛出异常
        print('with-body')
except Exception as ex:
    print(ex)

# 输出
enter
exit
division by zero

exit 返回True

class MyResource:

    def __enter__(self):        
        print('enter')
        return self
    
    def __exit__(self,exc_type, exc_value, exc_tb):
        print('exit')
        return True

try:
    with MyResource() as resource:
        1/0  #会抛出异常
        print('with-body')
except Exception as ex:
    print(ex)

# 输出
enter
exit

我们写了两组代码,一组在__exit__返回False,一组在__exit__返回True。通过两组输出,我们对比发现,当在__exit__方法中返回True时,try except会捕获不到异常,当返回值为False时,可以在try except捕获到异常。python为我们处理异常提供了非常灵活的方式,我们想在内部处理异常在__exit__方法中返回True,如果想在外面处理异常,在__exit__方法中返回False

7.with使用场景

相信大家到这里,也已经基本掌握with的用法。with 语句适用于对资源进行访问的场合,如访问文件,连接数据库,线程等都比较适合使用with。下面为大家写个用with来处理数据库连接的代码

import psycopg2

class MyPgsql:

    def __enter__(self):
        #建立数据库连接
        self.conn = psycopg2.connect(database='GIS_GEO_DATA',user='postgres',password='1qa2ws3ed',host='103.248.102.20',port='5432')
        self.cur = conn.cursor()
        return self
    
    def __exit__(self,exc_type,exc_value,exc_tb):
        #关闭数据库连接
        self.cur.close()
        self.conn.close()
        return True
    
    def qurey(self):
        #操作查询
        print('查询数据库')
        self.cur.execute(tmp)
        self.conn.commit()

with MyPgsql() as pg:
    pg.qurey()

8.contextlib简化上下文管理器

上面讲定义一个上下文管理器需要__enter____exit__方法。其实python中给我们提供一个contextlib库,将一个函数包装成上下文管理器。

import contextlib

@contextlib.contextmanager
def file_open(file_name):
    print("file open")
    yield {}
    print("file end")


with file_open("test.txt") as f:
    print("file processing")

python3.7 入门教程

Python 准备工作(1) Python 基本数据类型 Number(2) Python 字符串 str(3) Python 列表List(4) Python 元组tuple(5) Python 集合set(6) Python 字典 dict(7) Python 变量(8) Python 运算符(9) Python 条件语句(10) Python 循环语句(11) Python 包 模块(12) Python 函数一(13) Python 函数二(14) Python 面向对象(15) Python 正则(16) Python json(17) Python 枚举(18) Python 闭包(19) Python 装饰器(20) Python 杂记(21) Python with(22)