我們還沒有談到__exit__方法的這三個參數:type, value和traceback。
在第4步和第6步之間,如果發生異常,Python會將異常的type,value和traceback傳遞給__exit__方法。
它讓__exit__方法來決定如何關閉文件以及是否需要其他步驟。在我們的案例中,我們並沒有注意它們。
那如果我們的文件對象拋出一個異常呢?萬一我們嘗試訪問文件對象的一個不支持的方法。舉個例子:
with File('demo.txt', 'w') as opened_file:
opened_file.undefined_function('Hola!')
我們來列一下,當異常發生時,with語句會采取哪些步驟。
1. 它把異常的type,value和traceback傳遞給__exit__方法
2. 它讓__exit__方法來處理異常
3. 如果__exit__返回的是True,那麼這個異常就被優雅地處理了。
4. 如果__exit__返回的是True以外的任何東西,那麼這個異常將被with語句拋出。
在我們的案例中,__exit__方法返回的是None(如果沒有return語句那麼方法會返回None)。因此,with語句拋出了那個異常。
Traceback (most recent call last):File "<stdin>", line 2, in <module>
AttributeError: 'file' object has no attribute 'undefined_function'
我們嘗試下在__exit__方法中處理異常:
class File(object):
def __init__(self, file_name, method):
self.file_obj = open(file_name, method)
def __enter__(self):
return self.file_obj
def __exit__(self, type, value, traceback):
print("Exception has been handled")
self.file_obj.close()
return True
with File('demo.txt', 'w') as opened_file:
opened_file.undefined_function()
# Output: Exception has been handled
我們的__exit__方法返回了True,因此沒有異常會被with語句拋出。
這還不是實現上下文管理器的唯一方式。還有一種方式,我們會在下一節中一起看看。
我們還可以用裝飾器(decorators)和生成器(generators)來實現上下文管理器。
Python有個contextlib模塊專門用於這個目的。我們可以使用一個生成器函數來實現一個上下文管理器,而不是使用一個類。
讓我們看看一個基本的,沒用的例子:
from contextlib import contextmanager
@contextmanager
def open_file(name):
f = open(name, 'w')
yield f
f.close()
OK啦!這個實現方式看起來更加直觀和簡單。然而,這個方法需要關於生成器、yield和裝飾器的一些知識。在這個例子中我們還沒有捕捉可能產生的任何異常。它的工作方式和之前的方法大致相同。
讓我們小小地剖析下這個方法。
1. Python解釋器遇到了yield關鍵字。因為這個緣故它創建了一個生成器而不是一個普通的函數。
2. 因為這個裝飾器,contextmanager會被調用並傳入函數名(open_file)作為參數。
3. contextmanager函數返回一個以GeneratorContextManager對象封裝過的生成器。
4. 這個GeneratorContextManager被賦值給open_file函數,我們實際上是在調用GeneratorContextManager對象。
那現在我們既然知道了所有這些,我們可以用這個新生成的上下文管理器了,像這樣:
with open_file('some_file') as f:
f.write('hola!')
至此,Python進階已結束,感謝支持。