DAY 23. python上下文管理器
Python 的 with 語句支持通過上下文管理器所定義的運行時上下文這一概念。 此對象的實現使用了一對專門方法,允許用戶自定義類來定義運行時上下文,在語句體被執行前進入該上下文,并在語句執行完畢時退出該上下文:
實現了__enter__()
和__exit__(exc_type, exc_val, exc_tb)
方法的對象就是上下文管理器,上下文管理器可以被with支持。
class Demo:def __init__(self):print("init")def __enter__(self):print("enter")def __exit__(self, exc_type, exc_val, exc_tb):print("exit")if __name__ == '__main__':demo = Demo()with demo:print("with")
__enter__()
的返回值將會賦值給與with配套使用的as后面的變量,__exit__()
如果返回True會忽略with中拋出的所有異常,返回False(默認)會向下傳遞異常,三個參數exc_type, exc_val, exc_tb分別表示捕捉到的異常類型,異常值,回溯信息,沒有異常為None。其行為類似于try,finally語句,不管有沒有產生異常,exit一定會被執行。
class Demo:def __init__(self):print("init")def __enter__(self):print("enter")return selfdef __exit__(self, exc_type, exc_val, exc_tb):print(exc_type, exc_val, exc_tb)print("exit")if __name__ == '__main__':demo = Demo()with demo as d:print(d)raise IOError("主動拋出異常")print("with")
結果:
Traceback (most recent call last):
initFile "E:/桌面文件/筆記/Note/Python/總結/code/DAY23/DAY23_1.py", line 18, in <module>
enterraise IOError("主動拋出異常")
<__main__.Demo object at 0x00000243B4005908>
OSError: 主動拋出異常
<class 'OSError'> 主動拋出異常 <traceback object at 0x00000243BAEDD048>
exit
__exit__() return True
后的運行結果
init
enter
<__main__.Demo object at 0x00000183AFDA11D0>
<class 'OSError'> 主動拋出異常 <traceback object at 0x00000183B6E6F908>
exit
傳入的異常絕對不應當被顯式地重新引發 —— 相反地,此方法應當返回一個假值以表明方法已成功完成并且不希望屏蔽被引發的異常.
這允許上下文管理代碼方便地檢測__exit__()
方法是否確實已失敗。
generator與contextlib.contextmanager
Python 的
generator
和contextlib.contextmanager
裝飾器提供了實現這些協議的便捷方式。 如果使用 contextlib.contextmanager 裝飾器來裝飾一個生成器函數,它將返回一個實現了必要的__enter__()
and__exit__()
方法的上下文管理器,而不再是由未經裝飾的生成器函數所產生的迭代器。
contextlib.contextmanager 是一個裝飾器,它可以不用定義類或__enter__()
和__exit__(exc_type, exc_val, exc_tb)
方法而產生一個上下文管理器,被裝飾的函數必須是一個生成器對象,并且這個迭代器只能yield出一個對象,它會被綁定到with語句as后面的變量上
from contextlib import contextmanager@contextmanager
def my_open(path: str, mode: str):# 之所以之捕捉了yield語句的異常,是因為我們只希望如果with語句塊中# 產生了異常,也可以確保close()被執行,至于open可能拋出的異常,我們希望它# 能夠向下傳遞。fp = open(path, mode)try:yield ffinally:print("close the file")fp.close()if __name__ == '__main__':with my_open('01.txt', 'w') as fp:raise OSErrorfp.write("111")
代碼執行順序是:
- 執行yield之前的語句
- yield調用后執行with中的代碼塊
- 最后執行yield之后的語句
closing()
closing()是contextlib中的一個方法,用來把一個不是上下文對象的方法變成上下文對象,也是用contextmanage實現的,一個官方的栗子
from contextlib import closing
from urllib.request import urlopenwith closing(urlopen('http://www.python.org')) as page:for line in page:print(line)
不用顯式調用page.close()也能確保執行
suppress()
可以選擇禁止一個或多個異常
if __name__ == '__main__':with suppress(OSError):with my_open('01.txt', 'r') as fp:# 拋出的這個異常會被忽略raise OSErrorfp.write("111")
redirect_stdout/redirect_stderr
重定向輸入輸出
# 將輸出重定向到文件from contextlib import redirect_stdoutpath = "test/test.txt"with open(path,"w") as fobj:with redirect_stdout(fobj):help(open)