[解決!Python]zipfileモジュールを使ってZIPファイルに書き込みを行うには解決!Python

PythonにはZIPファイルを読み書きするためのzipfileモジュールが標準で付属している。これを使ってZIPファイルを作成する方法を紹介する。

» 2023年11月21日 05時00分 公開
[かわさきしんじDeep Insider編集部]
「解決!Python」のインデックス

連載目次

# テスト用ファイルの準備
from pathlib import Path

foo = Path('foo.txt')
foo.write_text('foo.txt')
bar = Path('bar.txt')
bar.write_text('bar.txt')
Path('a').mkdir(exist_ok=True)
baz = Path('a/baz.txt')
baz.write_text('a/baz.txt')

# ZipFileクラスの使い方の基本型
import zipfile
from zipfile import ZipFile

zf = ZipFile('sample.zip', 'w')
zf.write(foo)  # sample.zipファイルにfoo.txtファイルを追加
zf.write('bar.txt'# sample.zipファイルにbar.txtファイルを追加
zf.close()

zf = ZipFile('sample.zip')
zf.printdir()
zf.close()
# 出力結果:
#File Name                                             Modified             Size
#foo.txt                                        2023-11-17 09:01:44            7
#bar.txt                                        2023-11-17 09:01:44            7

# with文を使ってZIPファイルを作成
with ZipFile('sample.zip', 'w') as zf:
    zf.write(foo)  # sample.zipファイルにfoo.txtファイルを追加
    zf.write(baz)  # sample.zipファイルにa/baz.txtファイルを追加

with ZipFile('sample.zip') as zf:  # ZIPファイルの内容を表示
    zf.printdir()
# 出力結果:
#File Name                                             Modified             Size
#foo.txt                                        2023-11-17 09:01:44            7
#a/baz.txt                                      2023-11-17 09:01:44            9

# アーカイブ名の指定
with ZipFile('sample.zip', 'w') as zf:
    zf.write(baz, arcname='b/baz.txt')

with ZipFile('sample.zip') as zf:  # ZIPファイルの内容を表示
    zf.printdir()
# 出力結果:
#File Name                                             Modified             Size
#b/baz.txt                                      2023-11-17 09:13:22            9

# 圧縮方法の指定
with ZipFile('sample.zip', 'w', compression=zipfile.ZIP_LZMA) as zf:
    zf.write(foo)

# 圧縮レベルの指定
with ZipFile('sample.zip', 'w', compression=zipfile.ZIP_DEFLATED,
              compresslevel=-1) as zf:
    zf.write(foo)

# 既存のZIPファイルへのファイルの追加
hoge = Path('hoge.txt')
hoge.write_text('hoge.txt')

with ZipFile('sample.zip') as zf:  # ZIPファイルの内容を表示
    zf.printdir()
# 出力結果:
#File Name                                             Modified             Size
#foo.txt                                        2023-11-17 09:50:44            7

with ZipFile('sample.zip', 'a') as zf:
    zf.write(hoge)

with ZipFile('sample.zip') as zf:  # ZIPファイルの内容を表示
    zf.printdir()
# 出力結果:
#File Name                                             Modified             Size
#foo.txt                                        2023-11-17 09:50:44            7
#hoge.txt                                       2023-11-17 09:51:24            8

# 既存のZIPファイル内に新規にファイルを作成し、そこにデータを書き込み
with ZipFile('sample.zip', 'a') as zf:
    with zf.open('huga.txt', 'w') as f:
        for w in ['foo', 'bar', 'baz']:
            f.write(w.encode())

with ZipFile('sample.zip') as zf:  # ZIPファイルの内容を表示
    zf.printdir()
# 出力結果:
#File Name                                             Modified             Size
#foo.txt                                        2023-11-17 10:08:52            7
#hoge.txt                                       2023-11-17 10:09:30            8
#huga.txt                                       1980-01-01 00:00:00            9

with ZipFile('sample.zip', 'a') as zf:
    result = '\n'.join(['foo', 'bar', 'baz'])
    zf.writestr('hogehoge.txt', result.encode())

with ZipFile('sample.zip') as zf:  # ZIPファイルの内容を表示
    zf.printdir()
# 出力結果:
#File Name                                             Modified             Size
#foo.txt                                        2023-11-17 10:08:52            7
#hoge.txt                                       2023-11-17 10:09:30            8
#huga.txt                                       1980-01-01 00:00:00            9
#hogehoge.txt                                   2023-11-17 10:12:30           11

import json
d = {'name': 'deep insider', 'addr': 'japan'}
content = json.dumps(d)
print(content)  # {"name": "deep insider", "addr": "japan"}

with ZipFile('data.zip', 'w') as zf:
    zf.writestr('data.bin', content)

with ZipFile('data.zip') as zf:  # ZIPファイルの内容を表示
    zf.printdir()
# 出力結果:
#File Name                                             Modified             Size
#data.bin                                       2023-11-17 10:14:44           41

with ZipFile('data.zip') as zf:
    result = zf.read('data.bin').decode()
    result = json.loads(result)
    print(result)  # {'name': 'deep insider', 'addr': 'japan'}


ZipFileクラスによるZIPファイルへの書き込みの基本

 Pythonには標準でzipfileモジュールが付属していて、このモジュールを使うことで簡単にZIPファイルを読み書きできる。今回はZIPファイルの作成、既存のZIPファイルへのファイルの追加などの方法を紹介する。ZIPファイルの読み込みについては別途紹介する。また、ここでは以下のファイルを事前に作成して、それらをZIPファイルに追加する際のサンプルファイルとする。

from pathlib import Path

foo = Path('foo.txt')
foo.write_text('foo.txt')
bar = Path('bar.txt')
bar.write_text('bar.txt')
Path('a').mkdir(exist_ok=True)
baz = Path('a/baz.txt')
baz.write_text('a/baz.txt')


 zipfileモジュールにはZipFileクラスがあり、このクラスを使ってZIPファイルを作成し、そのwriteメソッドでZIPファイルに既存のファイルを追加したり、プログラム内で使用しているデータをファイルとして追加したりするのが、その基本的な使い方となる。以下はその例だ。

import zipfile
from zipfile import ZipFile

import zipfile
from zipfile import ZipFile

zf = ZipFile('sample.zip', 'w')
zf.write(foo)  # sample.zipファイルにfoo.txtファイルを追加
zf.write('bar.txt'# sample.zipファイルにbar.txtファイルを追加
zf.close()


 この例では、ZipFileクラスのインスタンス生成時にZIPファイルの名前('sample.zip')と書き込みを意味する'w'を渡している。ZIPファイルのインスタンス作成時にはモードとして以下を指定できる。

  • 'w':ZIPファイルを新規に作成する。同名のファイルがあれば削除される
  • 'a':既存のZIPファイルの末尾に追加する
  • 'x':指定した名前のファイルが存在しない場合にのみ、そのファイルを作成する。既にファイルが存在しているときにはFileExistsError例外が発生する
  • 'r':既存のZIPファイルを読み込む(デフォルト値)

 その後、上で述べたようにwriteメソッドでfoo.txtファイルとbar.txtファイルをそのZIPファイルに追加している。ZIPファイルの取り扱いが終わったら、closeメソッドを呼ぶのを忘れないようにしよう。

 上のコードで作成されたZIPファイルの内容を確認してみよう(ZIPファイルの読み込みについては後日に紹介予定)。

zf = ZipFile('sample.zip')
zf.printdir()
zf.close()
# 出力結果:
#File Name                                             Modified             Size
#foo.txt                                        2023-11-17 09:01:44            7
#bar.txt                                        2023-11-17 09:01:44            7


 この例では、ZIPファイルを読み込みモードでオープンし、printdirメソッドでその内容を一覧している。先ほど追加した2つのファイルが確認できる。

 ZIPファイルはコンテキストマネジャーとしても機能するので、with文と組み合わせることで、ZIPファイルのオープンとクローズが管理できる。以下に例を示す。

with ZipFile('sample.zip', 'w') as zf:
    zf.write(foo)  # sample.zipファイルにfoo.txtファイルを追加
    zf.write(baz)  # sample.zipファイルにa/baz.txtファイルを追加

with ZipFile('sample.zip') as zf:  # ZIPファイルの内容を表示
    zf.printdir()
# 出力結果:
#File Name                                             Modified             Size
#foo.txt                                        2023-11-17 09:01:44            7
#a/baz.txt                                      2023-11-17 09:01:44            9


 writeメソッドではarcnameパラメーターにアーカイブ名を指定できる。これはファイルシステム中におけるファイルの階層構造とは異なる形で、ファイルをZIPファイルに追加したいときに使用する。例えば、以下はサブディレクトリaに存在するbaz.txtファイルを、ZIPファイル内ではサブディレクトリbの下に追加する。

with ZipFile('sample.zip', 'w') as zf:
    zf.write(baz, arcname='b/baz.txt')

with ZipFile('sample.zip') as zf:  # ZIPファイルの内容を表示
    zf.printdir()
# 出力結果:
#File Name                                             Modified             Size
#b/baz.txt                                      2023-11-17 09:13:22            9


圧縮方法の指定

 圧縮方法はZipFileクラスのインスタンス生成時にcompressionパラメーターで指定できる。ZipFileクラスでは以下の圧縮方法を指定できる。

  • zipfile.ZIP_STORED:圧縮しないで、ファイルをZIPファイルにまとめるだけ
  • zipfile.ZIP_DEFLATED:通常のZIP圧縮
  • zipfile.ZIP_BZIP2:BZIP2圧縮
  • zipfile.ZIP_LZMA:LZMA圧縮

 また、zipfile.ZIP_DEFLATEDとzipfile.ZIP_BZIP2のいずれかを圧縮方法とした場合、compresslevelパラメーターで圧縮レベルを指定可能だ。

圧縮方法 compresslevelに指定可能な値
zipfile.ZIP_DEFLATED 0から9、または-1。1が高速で圧縮は最小限、9は低速で圧縮を最大限に行う。0は圧縮をしない。-1は速度の圧縮のバランスを取る値となる(現在は6)
zipfile.ZIP_BZIP2 1から9。1が最低の圧縮率で、9が最大の圧縮率となる。デフォルト値は9
compresslevelに指定可能な値

 以下に例を示す。

# 圧縮方法の指定
with ZipFile('sample.zip', 'w', compression=zipfile.ZIP_LZMA) as zf:
    zf.write(foo)

# 圧縮レベルの指定
with ZipFile('sample.zip', 'w', compression=zipfile.ZIP_DEFLATED,
              compresslevel=-1) as zf:
    zf.write(foo)


 なお、writeメソッドでZIPファイルに個々のファイルを追加する際にcompress_typeパラメーターおよびcompresslevelパラメーターに値を指定して、上記の設定を上書きすることも可能だ。

ZIPファイルに追記

 既存のZIPファイルにファイルを追加するにはZipFileクラスのインスタンス生成時にmodeパラメーターに'a'を指定する。

 例えば、以下のようにして作成したファイルを、上で作成したZIPファイルに追記することを考えてみる。

hoge = Path('hoge.txt')
hoge.write_text('hoge.txt')


 先ほど作成したZIPファイルにはfoo.txtファイルのみが含まれている。

with ZipFile('sample.zip') as zf:  # ZIPファイルの内容を表示
    zf.printdir()
# 出力結果:
#File Name                                             Modified             Size
#foo.txt                                        2023-11-17 09:50:44            7


 ここでmodeパラメーターに'a'を指定して、ZipFileクラスのインスタンスを生成し、writeメソッドで上のhoge.txtファイルを追加してみよう。

with ZipFile('sample.zip', 'a') as zf:
    zf.write(hoge)


 ZIPファイルの内容を確認すると、次のように末尾にこのファイルが追加されていることが分かる。

with ZipFile('sample.zip') as zf:  # ZIPファイルの内容を表示
    zf.printdir()
# 出力結果:
#File Name                                             Modified             Size
#foo.txt                                        2023-11-17 09:50:44            7
#hoge.txt                                       2023-11-17 09:51:24            8


 また、既存のZIPファイル内で新しくファイルを作成して、そこにデータを書き込むことも可能だ。以下に例を示す。

with ZipFile('sample.zip', 'a') as zf:
    with zf.open('huga.txt', 'w') as f:
        for w in ['foo', 'bar', 'baz']:
            f.write(w.encode())

with ZipFile('sample.zip') as zf:  # ZIPファイルの内容を表示
    zf.printdir()
# 出力結果:
#File Name                                             Modified             Size
#foo.txt                                        2023-11-17 10:08:52            7
#hoge.txt                                       2023-11-17 10:09:30            8
#huga.txt                                       1980-01-01 00:00:00            9


 この例では、ZIPファイル内にhuga.txtというエントリを作成している。このエントリはバイナリファイルのように扱え、writeメソッドでそこにデータを書き込める。ただし、バイナリファイルのように扱えるということは、writeメソッドで文字列ではなくbytes列を書き込む必要がある。そのため、上のコード例では文字列をencodeメソッドでUTF-8形式のbytes列にエンコードしている。

 こちらの方法では、ファイルハンドルを使って、プログラム的に処理をした何らかのデータを必要に応じて何度でもZIPファイル内に作成したファイルに書き込むことが可能だ。

 一方、次のようにwritestrメソッドに書き込み対象のファイルと書き込むデータを指定して、一気に書き込むことも可能だ。

with ZipFile('sample.zip', 'a') as zf:
    result = '\n'.join(['foo', 'bar', 'baz'])
    zf.writestr('hogehoge.txt', result.encode())

with ZipFile('sample.zip') as zf:  # ZIPファイルの内容を表示
    zf.printdir()
# 出力結果:
#File Name                                             Modified             Size
#foo.txt                                        2023-11-17 10:08:52            7
#hoge.txt                                       2023-11-17 10:09:30            8
#huga.txt                                       1980-01-01 00:00:00            9
#hogehoge.txt                                   2023-11-17 10:12:30           11


 この例では、事前に変数resultに書き込み内容(ここでは3つの文字列を改行文字を区切り文字として連結したもの)を作成して、一気にZIPファイル内のhogehoge.txtファイルに書き込んでいる。writestrメソッドは文字列を受け取るが、実際にはこれはUTF-8形式にエンコードされてから書き込まれる。

 こちらの方法では、openメソッドで取得したファイルハンドルを介してデータを繰り返し書き込むよりもコードが簡潔になるが、同じファイルに繰り返しデータを書き込むことはできない(複数のエントリがZIPファイル内に作成される)。

 プログラム内でのZIPファイルに書き込むデータをどのように作成するかにもよるが、2つの方法でZIPファイル内に新規にファイルを作成して、そこにデータを書き込めることは覚えておこう。

 例えば、プログラム内で辞書にデータを保存していて、これをZIPファイル(data.zip)に「data.bin」という名前で書き込みたいとする。

 以下がそのデータだ。

import json
d = {'name': 'deep insider', 'addr': 'japan'}
content = json.dumps(d)
print(content)  # {"name": "deep insider", "addr": "japan"}


 ここでは辞書をjsonモジュールのdumps関数を使って文字列化している。これをZIPファイル内のdata.binファイルに保存するには次のようなコードを実行する。

with ZipFile('data.zip', 'w') as zf:
    zf.writestr('data.bin', content)


 data.zipファイルの内容は次のようになる。

with ZipFile('data.zip') as zf:  # ZIPファイルの内容を表示
    zf.printdir()
# 出力結果:
#File Name                                             Modified             Size
#data.bin                                       2023-11-17 10:14:44           41


 ZIPファイルからdata.binファイルの内容を読み込んで、辞書に復元するコードを以下に示す。

with ZipFile('data.zip') as zf:
    result = zf.read('data.bin').decode()
    result = json.loads(result)
    print(result)  # {'name': 'deep insider', 'addr': 'japan'}


 ZIPファイル内に存在するdata.binファイルを指定してreadメソッドを呼び出して、得られた結果(bytes列)をdecodeメソッドで文字列化し、最後にjson.loadsで辞書に復元している。

「解決!Python」のインデックス

解決!Python

Copyright© Digital Advantage Corp. All Rights Reserved.

スポンサーからのお知らせPR

注目のテーマ

AI for エンジニアリング
「サプライチェーン攻撃」対策
1P情シスのための脆弱性管理/対策の現実解
OSSのサプライチェーン管理、取るべきアクションとは
Microsoft & Windows最前線2024
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。