[解決!Python]py7zrパッケージを使って7-Zip(.7z)アーカイブを展開したり、ファイルを読み込んだりするには解決!Python

7-Zip(.7z)形式のアーカイブを展開したり、そこに格納されているファイルを読み込んだり、各種情報を取得したりする方法を紹介する。

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

連載目次

# 準備
from pathlib import Path
Path('data').mkdir(exist_ok=True)
Path('data/foo.txt').write_text('hello')
Path('data/bar.txt').write_text('world')
Path('data/baz.bin').write_bytes(b'\x00\x01\x02')
Path('./sample.txt').write_text('sample')

import py7zr

with py7zr.SevenZipFile('data.7z', 'w') as archive:
    archive.writeall('data')
    archive.write('./sample.txt')

from shutil import rmtree

rmtree('data'# 作成したディレクトリ以下を削除
Path('sample.txt').unlink()  # カレントディレクトリに作成したファイルを削除

# .7zアーカイブの展開
from py7zr import SevenZipFile

with SevenZipFile('data.7z', mode='r') as archive:
    archive.extractall()  # カレントディレクトリに展開

with SevenZipFile('data.7z', mode='r') as archive:
    archive.extractall('data'# 展開するディレクトリを指定

# 特定のファイルだけを展開
targets = ['data/foo.txt', 'data/bar.txt']
with SevenZipFile('data.7z', mode='r') as archive:
    archive.extract(targets=targets)

targets = ['data', 'data/foo.txt', 'data/bar.txt']
with SevenZipFile('data.7z', mode='r') as archive:
    archive.extract(targets=targets)

# 展開先と展開対象を指定
with SevenZipFile('data.7z', mode='r') as archive:
    archive.extract(path='data', targets=targets)

# ソリッド圧縮されているので一度展開するとEOF状態になる
with SevenZipFile('data.7z', mode='r') as archive:
    archive.extract(targets=targets)  # 特定のファイルだけを展開
    archive.extract(targets=['data/baz.bin'])  # 例外が発生する

# 再度、extractメソッドなどを呼び出すには、resetメソッドでポインタを先頭に戻す
targets = ['data/foo.txt', 'data/bar.txt']
with SevenZipFile('data.7z', mode='r') as archive:
    archive.extract(targets=targets)  # 特定のファイルだけを展開
    archive.reset()  # EOF状態をリセットする
    archive.extract(targets=['data/baz.bin'])  # OK

# アーカイブに含まれているファイルの一覧を取得
with SevenZipFile('data.7z', mode='r') as archive:
    names = archive.getnames()
    print(names)
    # 出力結果:
    # ['data', 'data/bar.txt', 'data/baz.bin', 'data/foo.txt', 'sample.txt']

# パスワードが必要かどうかを調べる
with SevenZipFile('data.7z', mode='r') as archive:
    password_required = archive.needs_password()
    print('needs password:', password_required)  # needs password: False

# アーカイブ内のファイルを読み込む
with SevenZipFile('data.7z', mode='r') as archive:
    contents = archive.readall()
    for f, c in contents.items():
        print(f'filename: {f}')
        if f.endswith('.bin'):
            print(f'contents: {c.read().hex()}'# バイナリデータ
        else:
            print(f'contents: {c.read().decode()}'# テキストデータ
        print('---')
# 出力結果:
#filename: data/bar.txt
#contents: world
#---
#filename: data/baz.bin
#contents: 000102
#---
#filename: data/foo.txt
#contents: hello
#---
#filename: sample.txt
#contents: sample
#---

# アーカイブの情報を取得
with SevenZipFile('data.7z', mode='r') as archive:
    info = archive.archiveinfo()  # py7zr.ArchiveInfoオブジェクトを取得
    print(f'filename: {info.filename}')
    print(f'header_size: {info.header_size}')
    print(f'method_names: {info.method_names}')
    print(f'size: {info.size}')
    print(f'solid: {info.solid}')
    print(f'blocks: {info.blocks}')
    print(f'uncompressed: {info.uncompressed}')

# アーカイブ内のファイル情報を取得
with SevenZipFile('data.7z', mode='r') as archive:
    file_infos = archive.list()
    for info in file_infos:
        print(f'filename: {info.filename}')
        print(f'archivable: {info.archivable}')
        print(f'compressed: {info.compressed}')
        print(f'crc32: {info.crc32}')
        print(f'creationtime: {info.creationtime}')
        print(f'is_directory: {info.is_directory}')
        print(f'uncompressed: {info.uncompressed}')


py7zrパッケージ

 7-Zipはアーカイバ(ファイルの圧縮や展開を行う)であり、オープンソースのフリーソフトウェアとしてWindows用に配布されている。7-Zipが取り扱うアーカイブ(拡張子は「.7z」)をPythonから扱うにはPyPIなどで配布されているpy7zrを使用する。本稿では7-Zipアーカイブの展開とアーカイブ内のファイルの読み込みについて解説する。アーカイブの作成については「py7zrパッケージを使って7-Zip(.7z)形式のアーカイブにファイルを圧縮するには」を参照されたい。

 py7zrパッケージを使用するには「pip install py7zr」「py -m pip install py7zr」などのコマンドラインを実行してあらかじめこれをインストールしておく必要がある。また、ここでは以下のコードにより、アーカイブを作成し、それをサンプルのアーカイブとして使用している。

from pathlib import Path
Path('data').mkdir(exist_ok=True)
Path('data/foo.txt').write_text('hello')
Path('data/bar.txt').write_text('world')
Path('data/baz.bin').write_bytes(b'\x00\x01\x02')
Path('./sample.txt').write_text('sample')

import py7zr

with py7zr.SevenZipFile('data.7z', 'w') as archive:
    archive.writeall('data')
    archive.write('./sample.txt')

from shutil import rmtree

rmtree('data'# 作成したディレクトリ以下を削除
Path('sample.txt').unlink()  # カレントディレクトリに作成したファイルを削除


 カレントディレクトリにdataディレクトリを作成し、その下にfoo.txtファイル、bar.txtファイル、baz.binファイルを作成し、さらにカレントディレクトリにsample.txtファイルを作成している。.7zrアーカイブにはこれら(1つのディレクトリ、その下にある3つのファイル、カレントディレクトリにある1つのファイル)が格納される。

py7zr.SevenZipFileクラス

 py7zrパッケージはSevenZipFileクラスを提供しており、7-Zip(.7z)形式のアーカイブの読み取りや作成、展開、圧縮などの作業の大半はこのクラスのインスタンスを使って行える。以下にインスタンス生成の構文を示す。

py7zr.SevenZipFile(file, mode='r', filters=None, dereference=False, password=None)


 パラメーターの意味は次の通り。

  • file:アーカイブのファイル名を指定する
  • mode:アーカイブを読み込みモード('r')、書き込みモード('w')、追記モード('a')のどのモードでオープンするかの指定
  • filters:アーカイブにファイルを書き込む際の圧縮方法の指定
  • dereference:シンボリックリンクやハードリンクされているファイルについて、書き込み時にアーカイブにリンク先の実態を含めるか、リンクを含めるかの指定
  • password:パスワードの指定

 作成したSevenZipFileクラスのインスタンスでcloseメソッドを呼び出すことで、アーカイブの操作は終了する。また、後述するようにこのクラスはコンテキストマネジャーとして機能するので「with SevenZipFile(……):」のような記述により、closeメソッドの明示的な呼び出しを省略できる。

.7zアーカイブに含まれる全ファイルを展開

 .7zアーカイブを展開するには、アーカイブを読み取りモードでオープンして、extractallメソッドで全てのファイルを展開するのが一番簡単だ。以下に例を示す。

from py7zr import SevenZipFile

with SevenZipFile('data.7z', mode='r') as archive:
    archive.extractall()  # カレントディレクトリに展開


 この例ではアーカイブの内容をカレントディレクトリに展開している。特定のディレクトリの配下に展開したければ、そのディレクトリをextractallメソッドに指定する。

with SevenZipFile('data.7z', mode='r') as archive:
    archive.extractall('data'# 展開するディレクトリを指定


 この例ではdataディレクトリ以下にアーカイブの内容を展開することになる(よって、dataディレクトリの下にさらにdataディレクトリが作成され、そこにfoo.txtファイルなどが展開される)。

特定のファイルだけを展開

 特定のファイルだけを展開するのであれば、それらのファイル名(somedir/somefile.ext)を要素とするリストを作成して、そのリストをextractメソッドに渡す。以下に例を示す。

targets = ['data/foo.txt', 'data/bar.txt']
with SevenZipFile('data.7z', mode='r') as archive:
    archive.extract(targets=targets)


 筆者が試したところでは上のコードで展開が可能だったが、ドキュメントでは展開先のディレクトリを含めたリストを渡すように記述されている。その場合は次のように記述する。

targets = ['data', 'data/foo.txt', 'data/bar.txt']
with SevenZipFile('data.7z', mode='r') as archive:
    archive.extract(targets=targets)


 展開先のディレクトリをリストに含めても、['data', 'foo.txt', 'bar.txt']のような書き方をすると展開に失敗することには注意しよう。あくまでも、['data', 'data/foo.txt', 'data/bar.txt']のように記述する必要がある。

展開先と展開対象を指定

 extractメソッドで特定のファイルだけを、特定のディレクトリの下に展開するのであれば、pathパラメーターに展開先を、targetsパラメーターに展開対象のファイルを指定する。以下に例を示す。

targets = ['data', 'data/foo.txt', 'data/bar.txt'# 上と同じ

with SevenZipFile('data.7z', mode='r') as archive:
    archive.extract(path='data', targets=targets)


注意点

 .7z形式のアーカイブでは基本的に「複数のファイルを1つのブロックにまとめたもの」が圧縮の対象となる(ソリッド圧縮)。そのため、アーカイブをオープンしてから、一度extractallメソッドやextractメソッドを呼び出すと、ブロックは使い切ったものと見なされ、EOF状態になる。そのため、extractメソッドなどを複数回呼び出そうとすると例外が発生する。

with SevenZipFile('data.7z', mode='r') as archive:
    archive.extract(targets=targets)  # 特定のファイルだけを展開
    archive.extract(targets=['data/baz.bin'])  # 例外が発生する


 extractメソッドなどを複数回にわたって呼び出すには、その都度、resetメソッドでEOF状態をリセットする必要がある。

targets = ['data/foo.txt', 'data/bar.txt']
with SevenZipFile('data.7z', mode='r') as archive:
    archive.extract(targets=targets)  # 特定のファイルだけを展開
    archive.reset()  # EOF状態をリセットする
    archive.extract(targets=['data/baz.bin'])  # OK


アーカイブに含まれているファイルの一覧を取得

 アーカイブに含まれているディレクトリ/ファイルの一覧はgetnamesメソッドで取得できる。以下に例を示す。

with SevenZipFile('data.7z', mode='r') as archive:
    names = archive.getnames()
    print(names)
# 出力結果:
# ['data', 'data/bar.txt', 'data/baz.bin', 'data/foo.txt', 'sample.txt']


パスワードが必要かどうかを調べる

 アーカイブの展開などでパスワードが必要かどうかはneeds_passwordメソッドで調べられる。

with SevenZipFile('data.7z', mode='r') as archive:
    password_required = archive.needs_password()
    print('needs password:', password_required)  # needs password: False


アーカイブ内のファイルを読み込む

 アーカイブを展開するのではなく、アーカイブ内のファイルをプログラム中で読み込んで、何らかの処理を行いたいのであれば、readallメソッドやreadメソッドを使用する。ここではreadallメソッドを例に簡単な使い方を示す。

with SevenZipFile('data.7z', mode='r') as archive:
    contents = archive.readall()
    for f, c in contents.items():
        print(f'filename: {f}')
        if f.endswith('.bin'):
            print(f'contents: {c.read().hex()}'# バイナリデータ
        else:
            print(f'contents: {c.read().decode()}'# テキストデータ
        print('---')
# 出力結果:
#filename: data/bar.txt
#contents: world
#---
#filename: data/baz.bin
#contents: 000102
#---
#filename: data/foo.txt
#contents: hello
#---
#filename: sample.txt
#contents: sample
#---


 readallメソッドの戻り値は「ファイル名: バイナリデータ」のようにファイル名(文字列)をキーに、そのファイルの内容(bytes列)を値とする辞書になっている。そのため、上のコード例ではreadallメソッドの戻り値にitemsメソッドを適用してファイル名と内容を取り出してループ処理をしている。ループ内部では、ファイル名が'bin'で終わっていればそれがバイナリファイルだと判断して、それ用の処理を、ファイル名が'bin'以外で終わっていればテキストファイルとして処理している。

 data/baz.binファイルでは「c.read().hex()」というコードによりファイルの内容が16進数に変換され「000102」と出力されているが、他のファイルでは「c.read.decode()」によりbytes列がデコードされて文字列が出力されている点に注目しよう。

アーカイブの情報を取得

 最後にアーカイブ自体についての情報や、アーカイブに格納されている個別のファイルについての情報を取得する方法を紹介する(ただし、コードの説明は省略)。

# アーカイブの情報を取得
with SevenZipFile('data.7z', mode='r') as archive:
    info = archive.archiveinfo()  # py7zr.ArchiveInfoオブジェクトを取得
    print(f'filename: {info.filename}')
    print(f'header_size: {info.header_size}')
    print(f'method_names: {info.method_names}')
    print(f'size: {info.size}')
    print(f'solid: {info.solid}')
    print(f'blocks: {info.blocks}')
    print(f'uncompressed: {info.uncompressed}')

# アーカイブに含まれているファイルの情報を取得
with SevenZipFile('data.7z', mode='r') as archive:
    file_infos = archive.list()
    for info in file_infos:
        print(f'filename: {info.filename}')
        print(f'archivable: {info.archivable}')
        print(f'compressed: {info.compressed}')
        print(f'crc32: {info.crc32}')
        print(f'creationtime: {info.creationtime}')
        print(f'is_directory: {info.is_directory}')
        print(f'uncompressed: {info.uncompressed}')


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

解決!Python

Copyright© Digital Advantage Corp. All Rights Reserved.

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

注目のテーマ

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

RSSについて

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

メールマガジン登録

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