下午小伙伴問了一個有趣的問題, 怎么用 Python 的 with 語句同時打開多個文件?
首先, Python 本身是支持同時在 with 中打開多個文件的
with open('a.txt', 'r') as a, open('b.txt', 'r') as b:
print(a.read())
print(b.read())
當然, 小伙伴的問題不可能這么簡單, 他需要從打開一個列表中的所有文件, 而不是打開固定的一個文件, 這時候上面的語法顯然就沒法滿足要求了. 幸運的是 with 語句是遵循協議的, 我們只要自定義出__enter__
方法和__exit__
方法就可以使用 with 語句打開自定義的類.比如我們定義以下類, 用於同時打開多個文件:
#!/usr/bin/env python
# coding: utf-8
class open_many:
def __init__(self, files=None, mode='r'):
if files is None:
self._files = []
else:
self._files = files
self.mode = mode
self.fds = [] # fd is short for file descriptor
def __enter__(self):
print('-->enter')
for f in self._files:
print('-->opening file')
self.fds.append(open(f, self.mode))
return self.fds
def __exit__(self, exc_type, exc_val, traceback):
print('-->exit')
for fd in self.fds:
print('-->closing file')
fd.close()
if exc_type == ValueError:
print('-->exception: ' + str(exc_val))
return True
if __name__ == '__main__':
print('')
with open_many(['a.txt', 'b.txt'], 'r') as files:
for f in files:
print f.read()
print('')
with open_many() as files:
raise ValueError('captured')
print('')
with open_many() as files:
raise Exception('uncaptureable')
運行結果如下:
其中__enter__
方法會被進入 with 語句時調用, 返回值會作為 as 后面變量的值. __exit__
方法的三個參數分別是發生的異常的類型, 異常變量, 和發生異常時的 traceback. 如果在__exit__
中能夠處理異常, 那么應該返回True, 否則應該返回 False. 需要注意的是, 並不需要再次把異常拋出. 這里處理的異常實在 with 語句塊中發生的異常
顯然為了使用 with 還需要定義一個類有點過於復雜了, Python 還提供了另一種方法, 使用 contextlib.contextmanager 裝飾器和生成器來實現
#!/usr/bin/env python
# coding: utf-8
from contextlib import contextmanager
@contextmanager
def open_many(files=None, mode='r'):
if files is None:
files = []
try: #相當於__enter__
fds = []
for f in files:
fds.append(open(f, mode))
yield fds
except ValueError as e:
print(e)
finally: #相當於__exit__
for fd in fds:
fd.close()
if __name__ == '__main__':
with open_many(['a.txt', 'b.txt'], 'r') as files:
for f in files:
print(f.read())
此外, contextlib 中還提供了 closing 語句, 使用這個語句可以自動調用類的 close 方法.
#!/usr/bin/env python
# coding: utf-8
from contextlib import closing
class Connection:
def open(self):
print('opened')
def close(self):
print('closed')
if __name__ == '__main__':
with closing(Connection()) as con:
con.open()
# do stuff with con