facadeパターンは「複雑なシステムに対し、シンプルなインターフェースを提供する」パターンです。
facadeパターンは多くの人が意識して使っていることで、pythonを叩く親シェルを作るのもある種facadeパターンです。
ファイル解凍プログラムを作ります。
圧縮されたファイル(gzip, tarなど)を解凍するには、Pythonでは異なるメソッドを使用する必要がある。
ここでは、圧縮されたファイルに対し、共通のインターフェース(unpack()メソッド等)を作成する。
今回は、zip,tar,gz形式のファイルを作成し、それぞれ解凍したいと思います。
ファイルはtemp.zipからダウンロードしzipを解凍、または以下のコマンドで作成してください。
#!/bin/bash
mkdir -p temp
# make test.zip
mkdir -p _temp/test1
echo "bag1" > "temp/test1/bag1.txt"
echo "bag2" > "temp/test1/bag2.txt"
echo "bag3" > "temp/test1/bag3.txt"
cd "temp/"
zip -r "test.zip" "test1/"
cd ..
# make test.tar.gz
mkdir -p _temp/test2
echo "genome1" > "temp/test2/genome1.txt"
echo "genome2" > "temp/test2/genome2.txt"
echo "genome3" > "temp/test2/genome3.txt"
cd "temp/"
tar -zcvf "test.tar.gz" "test2/"
cd ..
# make test.gz
echo "test" > _temp/test
gzip "temp/test"
rm -r temp/test1
rm -r temp/test2
なお、例題およびサンプルコードは実践Python3を一部改変しています。
import os
import gzip
import tarfile
import zipfile
def main():
fname_zip = "_temp/test.zip"
fname_tar = "_temp/test.tar.gz"
fname_gz = "_temp/test.gz"
for fname in [fname_zip, fname_tar, fname_gz]:
with Archive(fname) as archive:
print(archive.names())
archive.unpack()
class Archive:
def __init__(self, filename):
self._names = None
self._unpack = None
self._file = None
self.filename = filename
@property
def filename(self):
return self.__filename
@filename.setter
def filename(self, name):
self.close()
self.__filename = name
def close(self):
if self._file is not None:
self._file.close()
def names(self):
if self._file is None:
self._prepare()
return self._names()
def unpack(self):
if self._file is None:
self._prepare()
self._unpack()
def _prepare(self):
if self.filename.endswith((".tar.gz", ".tar.bz2", ".tar.xz", ".zip")):
self._prepare_tarball_or_zip()
elif self.filename.endswith(".gz"):
self._prepare_gzip()
else:
raise ValueError("unreadable format: {}".format(self.filename))
def _prepare_tarball_or_zip(self):
def ext_all():
self._file.extractall()
if self.filename.endswith(".zip"):
self._file = zipfile.ZipFile(self.filename)
self._names = self._file.namelist
self._unpack = ext_all
else:
suffix = os.path.splitext(self.filename)[1]
self._file = tarfile.open(self.filename, "r:" + suffix[1:])
self._names = self._file.getnames
self._unpack = ext_all
def _prepare_gzip(self):
self._file = gzip.open(self.filename)
filename = os.path.splitext(self.filename)[0]
self._names = lambda: [filename]
def extractall():
with open(filename, "wb") as f:
f.write(self._file.read())
self._unpack = extractall
def __str__(self):
return "{}({})".format(self.filename, self._file is not None)
# 以下2つは、コンテキストマネジャーに必要なメソッド。
# 詳細は補足解説で説明しています
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.close()
if __name__ == "__main__":
main()
(特になし)
実行すると、実行したディレクトリにtest1とtest2のフォルダが、temp下にtestファイルができます。
サンプルコードはArchiveクラスだけな分、処理が若干複雑になっています。
以下にフローを書いてみました。
委譲を用いることにより、ある程度柔軟にソースコードが変更できるようにしています。
特になし
あまりこのクラス図に意味はありませんが、
ということを把握しておけば問題ないです。
省略
main()
文の中でコンテキストマネジャーというものが使用されています。
def main():
# ...
with Archive(fname) as archive:
print(archive.names())
archive.unpack()
with
文で呼び出されているのがコンテキストマネジャーです。
コンテキストマネジャーが呼び出されると、前処理(__enter__
)と後処理(__exit__
)を必ず行われます。
コンテキストマネジャーの動きを理解するために、以下のようなコードを書いてみました。
class Hoge:
def __init__(self):
pass
def huga(self):
print("huga")
def __enter__(self):
print("enter")
return self
def __exit__(self, exc_type, exc_value, traceback):
print("exit")
if __name__ == "__main__":
with Hoge() as h:
h.huga()
print("hoge")
実行結果は以下
enter
huga
hoge
exit
with文の前にenter, 最後に exit関数が動いているのがわかります。
今回は、enterでは特に何もしませんが、exitで必ずファイルをcloseしています。