上下文管理
__enter__
__exit__
文件的IO操作可以對文件對象使用上下文管理,使用with…as語法。
with open('test') as f:
pass
如果希望類也支持上下文管理,則需要定義兩個函數。
class A:
def __init__(self):
print('init')
def __enter__(self):
print('enter')
def __exit__(self,exc_type,exc_val,exc_tb):
print('exit')
with A() as a:
print(a) # a 此時爲None
# 輸出:
# init
# enter
# None
# exit
進入with語句塊時,會調用__enter__,退出with語句塊時,調用__exit__。
因此,退出exit函數時,可以做清理工作,比如關閉文件描述符。即使異常退出,也會執行上下文。因此,涉及到資源申請的操作,最好使用上下文管理語句。
注意:先實例化,才能調用enter。因爲enter是實例方法。 只有with語法時,纔會和enter,exit等函數相關,如此纔會開啓上下文。
上下文管理對象
當一個對象同時實現了__enter__和__exit__方法時,它就屬於上下文管理對象。
方法 | 意義 |
---|---|
__enter__ | 進入與此對象相關的上下文。如果存在此方法,with語法會把該方法返回值作爲綁定到as子句中指定的變量上 |
__exit__ | 退出與此對象相關的上下文,包含三個參數:異常類型,異常的值,異常的traceback。 |
class Point:
def __init__(self):
print('init')
def __enter__(self):
print('enter')
def __exit__(self,exc_type,exc_val,exc_tb):
print('exit')
with Point() as f:
raise Exception('error') # 即使拋出異常,也會執行exit。因此,上下文管理是安全的。即使sys.exit也是安全的
print('do sth.')
注意:如果__exit__返回True,會壓制異常。
函數的上下文和類的上下文
-
函數的上下文,是同一個對象:
f = open('1.py') with f as a: print(f == a) # 同一個對象 print(id(f),id(a))
-
類的上下文,則不同:
with A() as a: print(a) # None 返回__enter__的return值 with A(): # print(A()) # 返回實例 # a 和A() 是不同對象
上下文應用場景
-
增強功能
在代碼執行的前後增加代碼,以增強其功能。類似裝飾器的功能。
-
資源管理
打開了資源需要關閉,例如文件對象、網絡連接、數據庫連接等。
-
權限驗證
在執行代碼之前,做權限驗證。在__enter__中處理。
應用:使用上下文,實現函數計時器
class Timeit:
def __init__(self,fn):
self.fn = fn
def __enter__(self):
self.start = datetime.datetime.now()
return self.fn #
def __exit__(self, exc_type, exc_val, exc_tb):
delta = (datetime.datetime.now() - self.start).total_seconds()
print('{} took {}s'.format(self.fn.__name__,delta))
with Timeit(add) as f:
print(add(4,6))
contextlib.contextmanager
它是一個裝飾器,裝飾一個函數後可實現上下文管理。
而無需像類一樣實現__enter__和__exit__ 方法。
它對裝飾的函數是有要求的,必須有yield,也就是這個函數必須返回一個生成器,且只有yield一個值。
也就是這個裝飾器接收一個生成器對象作爲參數。
import contextlib
@contextlib.contextmanager
def foo():
print('enter') # 相當於__enter__()
yield 5 # yield的值只能有一個,相當於__enter__方法的返回值
print('exit') # 相當於__exit__()
with foo() as f:
print(f)
輸出:
enter
5
exit
f接收了yield語句的返回值。
但是很明顯無法保證exit的執行,如果產生了異常就直接退出。因此增加try finally。
import contextlib
@contextlib.contextmanager
def foo():
print('enter') # 相當於__enter__()
try:
yield 5 # yield的值只能有一個,相當於__enter__方法的返回值
finally:
print('exit') # 相當於__exit__()
with foo() as f:
raise Exception()
print(f)
如此保證了enter和exit的正常執行。
當yield發生處爲生成器函數增加了上下文管理,這是爲函數增加上下文機制的方式:
- 把yield之前的當做方法執行
- 把yield之後的當做__exit____方法執行
- 把yield的值作爲__enter__的返回值
因此,如果業務邏輯簡單可以使用函數加contextlib的裝飾器方式,如果業務複雜,則使用類的方式加__enter__和__exit__方法方便。
反射相關的魔術方法
__getattr__
__setattr__
__delattr__
__getatrribute__
具體可參考《淺談Python中的反射》