在〈Python 3 Tutorial 第五堂(1)Shit happens!〉中談過,可以使用 finally
來做一些資源收尾動作,其中的範例是:
import sys, logging
def for_each_line(file, do_action):
try:
for line in file:
do_action(line)
except:
logger = logging.getLogger(__name__)
logger.exception('未處理的例外')
finally:
file.close()
try:
file = open(sys.argv[1], 'r')
for_each_line(file, lambda line: print(int(line) + 10, end = ''))
except IndexError:
print('請提供檔案名稱')
print('範例:')
print(' python3.5 read.py your_file')
except FileNotFoundError:
print('找不到檔案 {0}'.format(sys.argv[1]))
自定義 with_as
實際上,使用 finally
來關閉資源的流程大同小異,你也許會想要定義一個更通用的 with_as
函式,來重用這個流程:
import sys, logging
def with_as(file, do_action):
try:
do_action(file)
finally:
file.close()
def print_each_line(file):
try:
for line in file:
print(line, end = '')
except:
logger = logging.getLogger(__name__)
logger.exception('未處理的例外')
try:
with_as(open(sys.argv[1], 'r'), print_each_line)
except IndexError:
print('請提供檔案名稱')
print('範例:')
print(' python3.5 read.py your_file')
except FileNotFoundError:
print('找不到檔案 {0}'.format(sys.argv[1]))
如上頭所示,有了這個自定義的 with_as
,日後你就可以像 with_as(open(sys.argv[1], 'r'), print_each_line)
這樣,直接重用資源關閉的流程。
with as 語法
實際上,Python 中就提供了 with as
語法,來看看採用這個語法後,上面的程式看起來會長什麼樣子:
import sys, logging
def print_each_line(file):
try:
for line in file:
print(line, end = '')
except:
logger = logging.getLogger(__name__)
logger.exception('未處理的例外')
try:
with open(sys.argv[1], 'r') as file:
print_each_line(file)
except IndexError:
print('請提供檔案名稱')
print('範例:')
print(' python3.5 read.py your_file')
except FileNotFoundError:
print('找不到檔案 {0}'.format(sys.argv[1]))
如果你不需要 print_each_line
函式,也可以直接寫成這樣:
import sys, logging
try:
with open(sys.argv[1], 'r') as file:
for line in file:
print(line, end = '')
except IndexError:
print('請提供檔案名稱')
print('範例:')
print(' python3.5 read.py your_file')
except FileNotFoundError:
print('找不到檔案 {0}'.format(sys.argv[1]))
except:
print('未知的錯誤,請洽管理員')
logger = logging.getLogger(__name__)
logger.exception('未處理的例外')
with as
負責關閉資源,若發生了例外,要看資源本身在定義時,是否抑制了例外,open
開啟的檔案來說是沒有抑制例外,因此在上面的例子中,若中間發生了例外,with as
會關閉資源,然後例外依舊向外傳播,而程式中捕捉例外後,以 print
顯示錯誤訊息給使用者觀看。
with as
原理
實際上,with as
不限使用於檔案,只要物件支援環境管理協定(Context Management Protocol),就可以使用 with as
語句。支援環境管理協定的物件,必須實作 __enter__()
與 __exit__()
兩個方法,這樣的物件稱之為環境管理員(Context Manager)。
with
陳述句一開始執行,就會進行 __enter__()
方法,該方法傳回的物件,可以使用 as
指定給變數(如果有的話),接著就執行 with
區塊中的程式碼,以下是個簡單示範:
class Resource:
def __init__(self, name):
self.name = name
def __enter__(self):
print(self.name, ' __enter__')
return self
def __exit__(self, type, value, traceback):
print(self.name, ' __exit__')
return False
with Resource('res') as resource:
print(resource.name)
如果 with
區塊中的程式碼發生了例外,則會執行 __exit__()
方法,並傳入三個引數,這三個引數的意義是例外類型、例外訊息以及 traceback
物件(可參考〈再看 try、raise〉中的說明)。此時 __exit__()
方法若傳回 False
,則例外會被重新丟出,否則例外就停止傳播,通常 __exit__()
會傳回 False
,以便在 with
之外還可以處理例外。
如果 with
區塊中沒有發生例外而執行完畢,則也是執行 __exit__()
方法,此時 __exit__()
的三個參數都接收到None
。就上面的例子來說,會如下依序顯示:
res __enter__
res
res __exit__
練習 9:使用 with as
在練習 8 的 pickle_ex.py 與 shelve_ex.py 原始碼中,是自行使用 finally
來關閉資源,請試著將之改寫為 with as
來自動關閉資源。