[解決!Python]バイナリファイルを読み書きするには:shelve編解決!Python

shelveモジュールを使って、辞書と同じ使い勝手で外部ファイルにオブジェクトを永続化したり、そこからオブジェクトを復元したりする方法を紹介する。

» 2021年06月08日 05時00分 公開
[かわさきしんじDeep Insider編集部]

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

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

連載目次

import shelve

data_file = 'mydata'
key = 'person_data'

person_data = [('kawasaki', 120), ('isshiki', 38)]

with shelve.open(data_file) as d:
    d[key] = person_data

with shelve.open(data_file) as d:
    data = d[key]

print(data)  # [('kawasaki', 120), ('isshiki', 38)]

# デフォルトでは読み書き両用でオープンされる
num_data = [1, 2, 3, 4, 5]

with shelve.open(data_file) as d:
    data = d[key]  # 読み込み
    print(data)  # [('kawasaki', 120), ('isshiki', 38)]
    d['num_data'] = num_data  # 書き込み
    nums = d['num_data'# 読み込み
    print(nums)  # [1, 2, 3, 4, 5]

# writeback=False
more_data = ('endo', 45)

with shelve.open(data_file) as d:
    data = d[key]
    data.append(more_data)  # 読み出したデータに追加
    print(d[key])  # [('kawasaki', 120), ('isshiki', 38)]
    d[key] = data  # 反映するには元のキーの値を置き換える必要がある
    print(d[key])  # [('kawasaki', 120), ('isshiki', 38), ('endo', 45)]

# writeback=True
one_more_data = ('shimada', 50)

with shelve.open(data_file, writeback=True) as d:
    data = d[key]
    data.append(one_more_data)  # Shelfオブジェクトへの操作はキャッシュされる
    print(d[key])  # [('kawasaki', 120), ('isshiki', 38), ('endo', 45), ('shimada', 50)]
    # closeメソッドかsyncメソッドの呼び出しで、キャッシュの内容が書き込まれる。
    # キャッシュサイズが大きくなると書き戻しに時間がかかる点には注意


shelveモジュールとは

 Pythonに標準で添付されているshelveモジュールを利用すると、Pythonの辞書と似た形式でオブジェクトを永続化できる。つまり「shelveオブジェクト[キー] = 値」のようにして「値」に指定したオブジェクトを外部ファイルに永続化したり、逆に「値 = shelveオブジェクト[キー]」として外部ファイルからオブジェクトを取り出したりできる(「shelve」は「棚に何かを置く」といった意味)。

 このとき、「キー」には文字列を指定する。「値」にはpickle化可能なオブジェクトなら何でも指定できる(内部ではpickleモジュールが使われているので、pickleモジュールと同様な安全でない操作が可能な点には注意)。

 shelveモジュールを使って、永続化を行う基本的な方法はそのopen関数を呼び出して、Shelfクラス(またはそのサブクラス)のインスタンスを取得して、それを用いて辞書的なアクセスを行うことだ。以下にshelve.open関数の構文を示す。

shelve.open(filename, flag='c', protocol='None', writeback=False)


 filenameには内部で使用するデータベースファイルを指定する(多くの場合は、filenameに指定した文字列に何らかの拡張子が付いたものが実際のファイル名になるだろう)。flagにはファイルをオープンするモードを指定する。指定できるのは以下の値。

  • 'r':読み込み専用
  • 'w':書き込み専用
  • 'c':読み書き両用
  • 'n':新規作成

 指定を省略した場合には'c'が指定されたものとして扱われ、ファイルは読み書き両用でオープンされる。protocolは内部で使用するpickleのプロトコルバージョンである。writebackをTrueにすると、open関数で取得したShelfオブジェクトに格納されているエントリ(キーと値の組)に対する操作がキャッシュされるようになる。キャッシュの内容は、closeメソッドかsyncメソッドを呼び出したときに外部ファイルへ書き込まれる。デフォルト値はFalseであり、キャッシュは行われない。それぞれの動作の違いについては後述)。

 open関数により取得したShelfオブジェクトには辞書的なアクセスを行い、オブジェクトの永続化や復元を行い、Shelfオブジェクトの使用が終わったらcloseメソッドを呼び出す。

# shelveモジュールの使い方
import shelve

d = shelve.open('somefile'# shelve.open関数でShelfオブジェクトを取得
d[somekey] = somevalue  # 書き込み(永続化)
somevalue = d[somekey]  # 復元
d.close()  # 使い終わったらcloseメソッドを呼び出す


 shelve.open関数はwith文と組み合わせて次のようにも書ける。

# with文と組み合わせる
import shelve

with shelve.open('somefile') as d:
    d[somekey] = somevalue
    somevalue = d[somekey]


 これにより、closeメソッドを明示的に呼び出す必要がなくなるので、以下ではこちらの書き方でサンプルを示す。

shelveモジュールを使ったオブジェクトの永続化と復元

 以下にshelveモジュールを使ってオブジェクトの永続化と復元を行う例を示す。ここでは永続化先のファイル名は「mydata」(mydata.db)として、永続化/復元を行うデータはタプルを要素とするリストにしてある。これを'person_data'というキーを使って永続化/復元する。

import shelve

data_file = 'mydata'
key = 'person_data'

person_data = [('kawasaki', 120), ('isshiki', 38)]

with shelve.open(data_file) as d:
    d[key] = person_data  # 永続化

with shelve.open(data_file) as d:
    data = d[key]  # 復元

print(data)  # [('kawasaki', 120), ('isshiki', 38)]


 この例では、永続化(書き込み)と復元(読み込み)を別々のwith文で行っているが、先ほども述べた通り、デフォルトではshelve.open関数は読み書き両用でファイルをオープンする。そのため、実際には次のようなコードも記述できる。

num_data = [1, 2, 3, 4, 5]

with shelve.open(data_file) as d:
    data = d[key]  # 読み込み
    print(data)  # [('kawasaki', 120), ('isshiki', 38)]
    d['num_data'] = num_data  # 書き込み
    nums = d['num_data'# 読み込み
    print(nums)  # [1, 2, 3, 4, 5]


writebackパラメーターの値による動作の違い

 shelve.open関数のwritebackパラメーターの値をTrueにすると、Shelfオブジェクトに格納されているエントリの操作がキャッシュされる。一方、Falseにすると、キャッシュはされず、Shelfオブジェクトを介した読み込み/書き込みは外部ファイルにすぐに反映される(デフォルト)。

 writebackパラメーターの値がTrueかFalseで、永続化と復元に関連する振る舞いが異なることがある。例えば、以下はこのパラメーターの値がFalseのときの振る舞いの例だ。

more_data = ('endo', 45)

with shelve.open(data_file) as d:
    data = d[key]
    data.append(more_data)  # 読み出したデータに追加
    print(d[key])  # [('kawasaki', 120), ('isshiki', 38)]
    d[key] = data  # 反映するには元のキーの値を置き換える必要がある
    print(d[key])  # [('kawasaki', 120), ('isshiki', 38), ('endo', 45)]


 ここでは、先ほど書き込みを行った外部ファイルをオープンして、データを取得し(「data = d[key]」行)、それにappendメソッドでデータを追加している。次の「print(d[key])」行では、直前に読み込みを行ったのと同じキーの値を出力しているが、この結果は「[('kawasaki', 120), ('isshiki', 38)]」となる。つまり、データを追加したことが外部ファイルには反映されていない。反映するには「d[key] = data」を実行する必要がある。

 一方、以下はwritebackパラメーターの値をTrueとした場合の振る舞いの例だ。

one_more_data = ('shimada', 50)

with shelve.open(data_file, writeback=True) as d:
    data = d[key]
    data.append(one_more_data)  # Shelfオブジェクトへの操作はキャッシュされる
    print(d[key])  # [('kawasaki', 120), ('isshiki', 38), ('endo', 45), ('shimada', 50)]


 with文のブロックの先頭3行では、同じ処理をしていることに注意されたい(追加しているデータ自体は異なる)。だが、「print(d[key])」行の出力結果は「[('kawasaki', 120), ('isshiki', 38), ('endo', 45), ('shimada', 50)]」となる。つまり、「d[key] = data」行を実行しないでも、データが反映されている(ように見える)ということだ。これが、writeback=Trueにした場合に、操作がキャッシュされるということだ。実際には、外部ファイルには反映はされておらず、キャッシュから適切な結果が得られるようにshelveモジュールが取り計らってくれている。

 writeback=Trueにした場合、Shelfオブジェクトを介して操作するデータを実際に扱うのではなく、そのキャッシュに対して操作を行うことになるので、最後にcloseメソッドを呼び出すか(またはwith文のブロックが終了するか)、どこか適切なタイミングでsyncメソッドを呼び出すまでは外部ファイルへのアクセスを抑制できる。上で見たように、writeback=Falseとしたときには、永続化したデータを取得してそれを変更したら、それをどこかの時点で(「d[key] = data」行で行っているように)明示的に書き戻さないといけないが、writeback=Trueではそうした処理を書かずにより直観的なコードを書けるようになる(と感じる人もいるだろう)。

 ただし、writeback=Trueとしたときには、上で見たような操作が全てキャッシュされるので、あまりに多くのエントリを読み書きするとキャッシュサイズが大きくなり、外部ファイルへそれらを反映するための時間がかかるかもしれない。キャッシュの有無で振る舞いが変わる(コードも変わる)ことと、外部ファイルへの反映にかかる時間などを考慮して、キャッシュを使用するかどうかは決めるようにしよう。

 実際にどのような振る舞いになっているかは、次のようなコードからある程度は推測できる(説明は省略。コメントを参照のこと)。

import shelve

mydict = {}
myshelf = shelve.Shelf(mydict, writeback=True# 外部ファイルではなく辞書を使用

mydata = [0, 1]
key = 'key'

# 内部の辞書にデータが保存され、キャッシュされた
myshelf[key] = mydata
print(myshelf.dict)  # {b'key': b'\x80\x03]q\x00(K\x00K\x01e.'}
print(myshelf.cache)  # {'key': [0, 1]}

# 操作の結果はキャッシュに反映されるが、辞書には反映されない
myshelf[key].append(2)
print(myshelf.dict)  # {b'key': b'\x80\x03]q\x00(K\x00K\x01e.'}
print(myshelf.cache)  # {'key': [0, 1, 2]}

# キーを指定して取得した値はキャッシュと同一のオブジェクト
mylist = myshelf[key]
print(mylist is myshelf.cache[key])  # True

# syncメソッドにより辞書に内容が反映され、キャッシュがクリアされる
myshelf.sync()
print(myshelf.dict)  # {b'key': b'\x80\x03]q\x00(K\x00K\x01K\x02e.'}
print(myshelf.cache)  # {}

myshelf.close()  # Shelfオブジェクトをクローズ


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

解決!Python

Copyright© Digital Advantage Corp. All Rights Reserved.

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

注目のテーマ

Microsoft & Windows最前線2025
AI for エンジニアリング
ローコード/ノーコード セントラル by @IT - ITエンジニアがビジネスの中心で活躍する組織へ
Cloud Native Central by @IT - スケーラブルな能力を組織に
システム開発ノウハウ 【発注ナビ】PR
あなたにおすすめの記事PR

RSSについて

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

メールマガジン登録

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