Pythonでもzipファイルを扱いたい。
そういう時は便利なモジュールがあります。
その名も zipfile.ZipFile というクラス
zipへのファイル読み込み・書き込みに便利です。
でもいくつかハマったポイントがあったので、
このZipFileの注意点・コード例とか載せておきます。
このページの目次
まずZipFileによるzip読み込みの基本について
これはFileクラスを拡張したものです。
つまりZipに特化したファイルクラスという感じ
例えば次のようにして使えます。
▼ zipを開いてファイルを一覧表示するコード例
1 2 3 4 |
from zipfile import ZipFile with ZipFile('hoge.zip', 'r') as zip: print(zip.namelist()) |
▼ とあるzipでの出力結果
1 |
['pack_icon.png', 'pack.mcmeta', 'data/202207270755/functions/hoge.mcfunction', 'data/202207270755/functions/fuga.mcfunction', 'data/202207270755/functions/piyo.mcfunction'] |
zipファイルを ZipFile('hoge.zip', 'r') のようにファイルパスとモードを指定して読み込み・書き込みできます。
▼ 選択できるモードは次の3つ
- 'r' モード
読み込み専用でzipを開くモード
- 'w' モード
新規で書き込み専用で開くモード
- 'a' モード
既存に書き込み専用で開くモード
モードに関しては普通のファイルと変わらないです。
このZipFileクラスでのZip操作でできること一覧
当然zipを開いて終わりじゃありません。
PythonのZipFileでは次のような処理ができます。
▼ zipfile.ZipFileでできること一覧
- zip内ファイルのメタ情報取得
- zipからファイルを読み込みする
- zipに対して新規ファイルを追加する
- zip内の特定ファイルだけ解凍する
- zip内の特定ファイルにパスワード設定
ライブラリを導入せずともzipが扱えるのが利点。
それだけzipへの需要は大きいってことでもあります。
実際、zipはあらゆる場面で使われてます。
▼ 実はあのファイルはzipだと知って驚いた例
- APKファイル
Androidでアプリ実体(クラス・マニフェスト・リソース)を定義している実行可能ファイル。実はその正体は拡張子を変えただけのzipファイルだった……
- mcpackファイル
MineCraft Pocketエディション(マイクラPE)で使われるデータパックの形式。この正体もメタデータ・関数・リソースなどのファイルをzip化したもの
気づいてないだけでzipが使われてる場面は多い。
だからPython標準でzip操作できるのはありがたいです。
ところがzipへの読み込み・書き込みはハマることが多い
でもZipFileの使い方はハマりポイントも多いです。
- なぜかファイルが読み込めない
- ファイルへの書き込みがよくわからない
- テキストファイルが上手く書き込めない
僕は始めはこういうことで少し苦戦しました。
だから以降ではハマった点をいくつか書いていきます。
注意点1.zip内の絶対パスは / から始まらない
zip内での絶対パスの表し方について
Linuxなどのシステムだと
/ がルートだし、
Windowsでも
C:\ などの目印がありますよね。
でもZipFileでは先頭にスラッシュ( / )不要です。
▼ ファイルパス一覧表示のコードを再掲
1 2 3 4 |
from zipfile import ZipFile with ZipFile('hoge.zip', 'r') as zip: print(zip.namelist()) |
▼ 各ファイルのパスは / から始まっていない
1 |
['pack_icon.png', 'pack.mcmeta', 'data/202207270755/functions/hoge.mcfunction', 'data/202207270755/functions/fuga.mcfunction', 'data/202207270755/functions/piyo.mcfunction'] |
このように先頭のスラッシュが一切不要です。
だから pack_icon.png だったらzipのルートディレクトリにあるということ。この点は既存のファイルシステムに慣れていると少し戸惑います。
zip内へのファイル読み込み・追加でも重要な点です。
注意点2.バイナリファイル読み込みはread()を使う
zip内に次のようなファイルを読み込みしたい時
- 画像 : png, jpeg, gif...
- 音声 : mp3, ogg...
- 動画 : avi, mp4, wav...
- 他バイナリファイル
つまり非テキストファイルの場合です。
それには ZipFile.read() を使います。
▼ ZipFile.read(name[, pwd]) の詳細
Return the bytes of the file name in the archive. name is the name of the file in the archive, or a ZipInfo object. The archive must be open for read or append. pwd is the password used for encrypted files and, if specified, it will override the default password set with setpassword(). Calling read() on a closed ZipFile will raise a RuntimeError.
引用元 : https://docs.python.org/2/library/zipfile.html#zipfile.ZipFile.read
▼ 例えばzip内部のpng画像読み込みの例
1 2 3 4 5 6 7 8 9 |
from zipfile import ZipFile with ZipFile('hoge.zip', 'r') as zip: ## zip内画像をバイナリ読み込み bytes = zip.read('path/to/hoge.png') ## それを実ディスク上に保存 file = File('/path/to/hoge.png', 'wb') file.write(bytes) |
バイナリの場合はこうするのが簡単です。
画像・動画を読んでpython的に処理したり、
実ディスク上にファイルを展開コピーしたり…
先ほど書いたようにパスにも注意です。
注意点3.テキストファイル読み込みはopen()を使う
テキストファイルの読み込みは注意が必要。
その場合は ZipFile.open() を使うのが確実です。
▼ ZipFile.open(name[, mode[, pwd]]) の詳細
Extract a member from the archive as a file-like object (ZipExtFile). name is the name of the file in the archive, or a ZipInfo object. The mode parameter, if included, must be one of the following: 'r' (the default), 'U', or 'rU'. Choosing 'U' or 'rU' will enable universal newline support in the read-only object. pwd is the password used for encrypted files. Calling open() on a closed ZipFile will raise a RuntimeError.
引用元 : https://docs.python.org/2/library/zipfile.html#zipfile.ZipFile.open
▼ 例えばzip内部のjsonファイル読み込みの例
1 2 3 4 5 6 7 8 9 10 |
from zipfile import ZipFile with ZipFile('hoge.zip', 'r') as zip: ## zip内のテキストファイルを開く fp = zip.open('path/to/hoge.json', 'r') content = fp.read().decode('UTF-8') ## JSONとして解析 data = json.loads(content) print(data) |
バイナリだったら ZipFile.read() で十分です。
でもテキストファイルで ZipFile.read() を使うとバイナリとして読み込みされるし、テキストに直すのも面倒なので僕はこの方法を使ってます。
※ もし訂正箇所があれば教えてください。
注意点4.バイナリファイル追加はwrite/writestrを使う
ZipFileには似たようなメソッドがあります。
- ZipFile.write()
- ZipFile.writestr()
この2つは次のような違いが存在します。
▼ 違いをまとめると次の通り
- ZipFile.write(filename[, arcname[, compress_type]])
実ディスク上にあるファイル(filename)をzipアーカイブ内のファイル(arcname)に対してファイル書き込みする。主に画像・音声・動画・その他バイナリファイルの書き込みに使う
- ZipFile.writestr(zinfo_or_arcname, bytes[, compress_type])
zipアーカイブのファイル(arcname)に対してバイト配列(bytes)を直接書き込める。もしzip内に該当ファイルがない場合は新規作成される
こういう違いですね。
だからzip自身へのバイナリファイル追加、
それにはwrite()/writestr()を使うことに注意です。
実際には次のような感じで使われます。
1 2 3 4 5 6 7 8 9 10 11 12 |
from zipfile import ZipFile with ZipFile('hoge.zip', 'r') as zip: ## 実ディスクからzipにファイルコピー zip.write('/hoge/hoge.png', 'hoge/hoge.png') ## バイナリデータをzipに新規追加 file = open('/hoge/hoge.png', "rb") bytes = file.read() zip.writestr('hoge/hoge.png', bytes) zip.close() |
先ほど書いたようにパスにも注意です。
zip絶対パスは先頭に
/ が必要ありません。
あとは用途に応じて2つを使い分けてください。
PythonでのZipFileモジュールの注意点まとめ
ということで注意点を箇条書き
特にバイナリ関係はハマりやすいです。
以上、Pythonでのzip読み込み・書き込みでした。