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

两篇不错的文章:

浅谈 Python 的 with 语句

上下文管理器

上下文管理

当一个对象同时实现了 __enter____exit__ 方法,它就属于上下文管理的对象。

class Point:
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print("{} 进入上下文".format(self.name))

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("{} 离开上下文".format(self.name))


with Point("silen"):
    print("执行体")

输出内容

silen 进入上下文
执行体
silen 离开上下文

with可以开启一个上下文运行环境,在执行前做一些准备工作,执行后做一些收尾工作,需要注意的是,with并不开启一个新的作用域。

上下文管理的安全性

使用sys.exit(1)with异常退出。

import sys


class Point:
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print("{} 进入上下文".format(self.name))

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("{} 离开上下文".format(self.name))


with Point("silen"):
    print("异常退出")
    sys.exit(1)
    print("执行体")

执行结果

silen 进入上下文
异常退出
silen 离开上下文

从执行结果可以看出,哪怕是sys.exit(1)退出了Python环境,依然执行了__exit__ 函数,说明上下文管理很安全

as 做了什么

使用with的时候,经常在在后面跟上一个as 起到什么作用呢?

import sys


class Point:
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print("{} 进入上下文".format(self.name))
        return self.name

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("{} 离开上下文".format(self.name))


# with Point("silen"):
#     print("异常退出")
#     sys.exit(1)
#     print("执行体")

p = Point("silen")
with p as pp:
    print(p == pp)
    print(p is pp)
    print(id(p), id(pp))
    print("p是{} , pp 是 {}".format(p,pp))

执行结果

silen 进入上下文
False
False
2172855121808 2172855628976
p是<__main__.Point object at 0x000001F9E841F790> , pp 是 silen
silen 离开上下文

可以看出,with语法会调用with后面对象的的__enter__方法,如果有as则将__enter__函数的返回值赋给as子句的变量,上例中等价于pp=p.__enter__().

__exit__方法的参数

__exit__(self, exc_type, exc_val, exc_tb) 方法有三个参数,都跟异常有关,如果with上下文退出时,没有异常,这三个参数的值都为None,如果有异常时:

  • exc_type 为异常类型
  • exc_val为异常值
  • exc_tb为异常的追踪信息,tbtraceback的简写
    如果 __exit__方法返回一个等效True 的值,则压制异常,否则,继续抛出异常。
import sys

class Point:
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print("{} 进入上下文".format(self.name))
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("exc_type 是 {}", exc_type)
        print("exc_val 是 {}", exc_val)
        print("exc_tb 是 {}", exc_tb)
        print("{} 离开上下文".format(self.name))
        return None  # 默认return None

# with Point("silen"):
#     print("异常退出")
#     sys.exit(1)
#     print("执行体")

p = Point("silen")
with p as pp:
    print("开始with")
    raise Exception("出异常了")
    print("结束with")

输出内容如下,可以看出,异常被抛出来了,

Traceback (most recent call last):
  File "D:/ws/python/start/src/base/面向对象/上下文对象.py", line 27, in <module>
    raise Exception("出异常了")
Exception: 出异常了
silen 进入上下文
开始with
exc_type 是 {} <class 'Exception'>
exc_val 是 {} 出异常了
exc_tb 是 {} <traceback object at 0x0000025ACF3903C0>
silen 离开上下文

修改上面的代码,返回True , 异常就看不到了,这里就不贴代码了。可以自行测试。

contextlib

contextlib的使用比较简单,可以参照文章开头的链接,这里主要看下在使用contextmanager 的时候,异常的处理。

from contextlib import contextmanager

@contextmanager
def show():
    print("enter方法内容")
    # yield 后面的值作为 __enter__ 函数的返回值
    yield "yield返回值"
    print("exit方法内容")

with show() as b:  # b的值是 yield 返回的
    print("with开始了")
    print(b)
    1/0
    print("with结束除了")

上面的代码,执行输出如下:

enter方法内容
with开始了
yield返回值
Traceback (most recent call last):
  File "D:/ws/python/start/src/base/面向对象/上下文对象.py", line 42, in <module>
    1/0
ZeroDivisionError: division by zero

可以看到函数show代码中,yield后面的print("exit方法内容")并没有执行,如果这句得不到执行,则意味着收尾的工作的无法进行,那么,这个异常应该怎么捕获并处理呢?

from contextlib import contextmanager

@contextmanager
def show():
    print("enter方法内容")
    try:
     	# yield 后面的值作为 __enter__ 函数的返回值
        yield "yield返回值"
    except ZeroDivisionError as e:
        print(e)
        print("************处理捕获的异常***************")
    finally:
        print("exit方法内容")


with show() as b:
    print("with开始了")
    print(b)
    1 / 0
    print("with结束除了")

看下输出结果,就知道该怎么处理with体内出现的异常了。

enter方法内容
with开始了
yield返回值
division by zero
************处理捕获的异常***************
exit方法内容

使用上下文管理器的时候,可以根据业务逻辑复杂的程度来选择,如果业务逻辑简单就使用函数式的,如果复杂就使用类方式的即可,

上下文管理器就到这里。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页