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

水深无声 2022-12-24 05:51 225阅读 0赞

两篇不错的文章:

浅谈 Python 的 with 语句

上下文管理器

上下文管理

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

  1. class Point:
  2. def __init__(self, name):
  3. self.name = name
  4. def __enter__(self):
  5. print("{} 进入上下文".format(self.name))
  6. def __exit__(self, exc_type, exc_val, exc_tb):
  7. print("{} 离开上下文".format(self.name))
  8. with Point("silen"):
  9. print("执行体")

输出内容

  1. silen 进入上下文
  2. 执行体
  3. silen 离开上下文

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

上下文管理的安全性

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

  1. import sys
  2. class Point:
  3. def __init__(self, name):
  4. self.name = name
  5. def __enter__(self):
  6. print("{} 进入上下文".format(self.name))
  7. def __exit__(self, exc_type, exc_val, exc_tb):
  8. print("{} 离开上下文".format(self.name))
  9. with Point("silen"):
  10. print("异常退出")
  11. sys.exit(1)
  12. print("执行体")

执行结果

  1. silen 进入上下文
  2. 异常退出
  3. silen 离开上下文

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

as 做了什么

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

  1. import sys
  2. class Point:
  3. def __init__(self, name):
  4. self.name = name
  5. def __enter__(self):
  6. print("{} 进入上下文".format(self.name))
  7. return self.name
  8. def __exit__(self, exc_type, exc_val, exc_tb):
  9. print("{} 离开上下文".format(self.name))
  10. # with Point("silen"):
  11. # print("异常退出")
  12. # sys.exit(1)
  13. # print("执行体")
  14. p = Point("silen")
  15. with p as pp:
  16. print(p == pp)
  17. print(p is pp)
  18. print(id(p), id(pp))
  19. print("p是{} , pp 是 {}".format(p,pp))

执行结果

  1. silen 进入上下文
  2. False
  3. False
  4. 2172855121808 2172855628976
  5. p是<__main__.Point object at 0x000001F9E841F790> pp silen
  6. 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:

    1. def __init__(self, name):
    2. self.name = name
    3. def __enter__(self):
    4. print("{} 进入上下文".format(self.name))
    5. return self
    6. def __exit__(self, exc_type, exc_val, exc_tb):
    7. print("exc_type 是 {}", exc_type)
    8. print("exc_val 是 {}", exc_val)
    9. print("exc_tb 是 {}", exc_tb)
    10. print("{} 离开上下文".format(self.name))
    11. return None # 默认return None

    with Point(“silen”):

    print(“异常退出”)

    sys.exit(1)

    print(“执行体”)

    p = Point(“silen”)
    with p as pp:

    1. print("开始with")
    2. raise Exception("出异常了")
    3. print("结束with")

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

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

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

contextlib

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

  1. from contextlib import contextmanager
  2. @contextmanager
  3. def show():
  4. print("enter方法内容")
  5. # yield 后面的值作为 __enter__ 函数的返回值
  6. yield "yield返回值"
  7. print("exit方法内容")
  8. with show() as b: # b的值是 yield 返回的
  9. print("with开始了")
  10. print(b)
  11. 1/0
  12. print("with结束除了")

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

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

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

  1. from contextlib import contextmanager
  2. @contextmanager
  3. def show():
  4. print("enter方法内容")
  5. try:
  6. # yield 后面的值作为 __enter__ 函数的返回值
  7. yield "yield返回值"
  8. except ZeroDivisionError as e:
  9. print(e)
  10. print("************处理捕获的异常***************")
  11. finally:
  12. print("exit方法内容")
  13. with show() as b:
  14. print("with开始了")
  15. print(b)
  16. 1 / 0
  17. print("with结束除了")

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

  1. enter方法内容
  2. with开始了
  3. yield返回值
  4. division by zero
  5. ************处理捕获的异常***************
  6. exit方法内容

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

上下文管理器就到这里。

发表评论

表情:
评论列表 (有 0 条评论,225人围观)

还没有评论,来说两句吧...

相关阅读