165:固定長レコードを処理する
struct
struct モジュールは Python オブジェクトと バイナリデータの相互変換をサポートするモジュールです。
データ作成
struct モジュールを使用して「4 バイトの整数 2 つと 8 バイトの文字列、計 16 バイト」というデータを作成するには pack 関数を用い、次のようにします。
>>> import struct
>>> struct.pack('=2i8s', 27240, 6514034, 'foo') # バイトオーダー環境依存
'hj\x00\x00rec\x00foo\x00\x00\x00\x00\x00'
>>>
>>> struct.pack('<2i8s', 27240, 6514034, 'foo') # リトルエンディアン
'hj\x00\x00rec\x00foo\x00\x00\x00\x00\x00'
>>>
>>> struct.pack('>2i8s', 27240, 6514034, 'foo') # ビッグエンディアン
'\x00\x00jh\x00cerfoo\x00\x00\x00\x00\x00'
pack 関数の第 1 引数 fmt には、作成するデータの構造を表す「フォーマット文字列」を指定します。この書式は struct モジュールのドキュメントを参照してください。データの並びのほか、バイトオーダー、アラインメントを指定することもでき、フォーマット文字列の最初に「@」、「=」、「<」、「>」、「!」のいずれかを使うことで指定します。
省略するとデフォルトの「@]を記述したものとみなされます。「@」指定時は「型サイズ、バイトオーダー、アラインメントすべて環境依存」の挙動をし、同環境の C プログラムにて作成されたデータを読むのに適しています。
しかし、今回の「固定長レコードを処理する」という目的には「=」「<」「>」らを用いたほうがよいでしょう。「=」指定時は「型サイズが一意に定まり(short は 2 バイト、 int, long は 4 バイトなど)、バイトオーダーは環境依存、アラインメントはなし」となるので挙動が明白なものとなります。「<」「>」はこれに加え、バイトオーダーも確定し、それぞれリトルエンディアン、ビッグエンディアンとなります。
次のコードは複数のレコードを順次作成し、ファイル data.dat に保存します。
filename = u'data.dat'
record_format = '=2i8s'
#record_size = 16
#assert record_size == struct.calcsize(record_format)
py_datas = [
[1, 10, 'abcdefgh'],
[2, 20, 'ijklmnop'],
[3, 30, 'qrstuvwx'],
[4, 40, 'yzABCDEF'],
]
fobj = open(filename, 'wb')
try:
for py_data in py_datas:
c_data = struct.pack(record_format, *py_data)
print repr(c_data)
fobj.write(c_data)
finally:
fobj.close()
データ読み込み
struct モジュールを使用して「4 バイトの整数 2 つと 8 バイトの文字列、計 16 バイト」というデータを読み込むには unpack 関数を用い、次のようにします。
>>> import struct
>>> record_native = struct.pack('=2i8s', 27240, 6514034, 'foo')
>>> record_little = struct.pack('<2i8s', 27240, 6514034, 'foo')
>>> record_big = struct.pack('>2i8s', 27240, 6514034, 'foo')
>>>
>>> struct.unpack('=2i8s', record_native) # バイトオーダー環境依存
(27240, 6514034, 'foo\x00\x00\x00\x00\x00')
>>> struct.unpack('<2i8s', record_little) # リトルエンディアン
(27240, 6514034, 'foo\x00\x00\x00\x00\x00')
>>> struct.unpack('>2i8s', record_big) # ビッグエンディアン
(27240, 6514034, 'foo\x00\x00\x00\x00\x00')
unpack 関数の第 1 引数 fmt には、読み出すデータの構造を表す「フォーマット文字列」を指定します。書式は pack 関数のものと同じです。上記の説明および struct モジュールのドキュメントを参照してください。
次のコードはファイル data.dat に保存された複数レコードを順次、読み出します。
filename = u'data.dat'
record_format = '=2i8s'
record_size = 16
assert record_size == struct.calcsize(record_format)
fobj = open(filename, 'rb')
py_datas = []
try:
while True:
c_data = fobj.read(record_size)
if not c_data:
break
py_data = struct.unpack(record_format, c_data)
print repr(py_data)
py_datas.append(py_data)
finally:
fobj.close()
任意の位置のデータにアクセスするには ファイルオブジェクト のドキュメント、特に seek メソッドの説明が役に立つはずです。次のコードはファイル data.dat の先頭と末尾のデータを交換します。
n = 2
filename = u'data.dat'
record_format = '=2i8s'
record_size = 16
fobj = open(filename, 'rb+')
try:
head = fobj.read(record_size)
fobj.seek(-record_size, 2) # 末尾データの先頭に移動
tail = fobj.read(record_size)
fobj.seek(0) # ファイルの先頭に移動
fobj.write(tail)
fobj.seek(-record_size, 2) # 末尾データの先頭に移動
fobj.write(head)
finally:
fobj.close()
次のコードはファイル data.dat に保存されたレコードから n 番目のデータを取り出します。
n = 2
filename = u'data.dat'
record_format = '=2i8s'
record_size = 16
fobj = open(filename, 'rb')
try:
fobj.seek(record_size * n)
c_data = fobj.read(record_size)
py_data = struct.unpack(record_format, c_data)
print repr(py_data)
finally:
fobj.close()