Pythonのバイナリデータの扱いについて

画面表示

printf

a = 15
print("%02x" % a)             #=>  0f   (16進数で表示)

b = "A"
print(ord(b))                 #=>  65   (10進数で表示)
print("0x%02x\n" % ord(b))    #=>  0x41

c = "a"
print(ord(c))                 #=>  97
print("0x%02x\n" % ord(c))    #=>  0x61

print(hex(ord(c))             #=>  0x61

ord

ord関数は、長さ1の文字列が8ビット文字列なら、バイト値を返します。

data = 'B'
print(ord(data))      #=>   66
ord関数と、chr関数の機能は逆になります。 そのため、chr(ord('B'))と、 ord(chr(66)) は、何も処理していないのと同じになります。

hex

hex関数は、数値を16進数で表示します。

data = 12
print(hex(data))      #=>  0xc

ファイル入力

バイナリファイルのデータを読みだす場合、readメソッドが使用できます。 しかし、readメソッドは読み込んだデータを文字列として扱うので、整数値として扱う場合は関数ordを使用します。

バイナリファイル "tmp.bin" の中身が16進数表示で 08 09 0A 0B 0C FF の場合

f = open('tmp.bin', 'rb')
while True:
    d = f.read(1)
    if len(d) == 0:
        break
    print ord(d),
    print '(%x) ' % ord(d),
f.close		
この実行結果は以下になります。
8 (8)  9 (9)  10 (a)  11 (b)  12 (c)  255 (ff)

Python3の場合は、以下のようにします。

with open('tmp.bin', 'rb') as f:
    while True:
        d = f.read(1)
        if len(d) == 0:
            break
        print('%s (%x) ' % (ord(d), ord(d)), end='')

ファイル出力

バイナリデータをファイルに出力する方法

バイト単位の出力

Python2では、ファイルにバイト単位でバイナリデータを書き込みたい場合, chr()を使用する方法があります。

data = [8, 9, 10, 11, 12, 255]
f = open('tmp.bin', 'wb')
for d in data:
    f.write(chr(d))        # 08 09 0a 0b 0c ff
f.close			
これを実行すると、ファイルサイズが5バイトのtmp.binができます。

Python3では、整数型に追加されたto_byteメソッドを使います。

data = [8, 9, 10, 11, 12, 255]
with open('tmp.bin', 'wb') as f:
    for d in data:
        f.write(d.to_bytes(1, byteorder='little'))   # 08 09 0a 0b 0c ff
  ・整数型における追加のメソッド・to_bytes

to_byteメソッドの第二引数は、バイトオーダーの文字列"big"、"little"、を指定します。 バイトオーダーが分からない場合は、現システムのバイトオーダーの文字列が入ったsys.byteorderを指定する方法があります。

import sys

data = [8, 9, 10, 11, 12, 255]
with open('tmp.bin', 'wb') as f:
    for d in data:
        f.write(d.to_bytes(1, byteorder=sys.byteorder))   # 08 09 0a 0b 0c ff

第一引数のバイト数が1の場合、バイトオーダーは"big"、または"little"のどちらを指定しても結果は変わりません。第一引数が2以上の場合は、バイトオーダーを正しく指定する必要があります。 以下はバイト数に2、バイトオーダーに"little"を指定した例です。

data = [8, 9, 10, 11, 12, 255]
with open('tmp.bin', 'wb') as f:
    for d in data:
        f.write(d.to_bytes(2, byteorder='little'))
$ hexdump -C tmp.bin
00000000  08 00 09 00 0a 00 0b 00  0c 00 ff 00              |............|
0000000c
sys.byteorder = "little"の場合、第一引数のバイト数を2とすると、上記のようにリトルエンディアンになります。(0x0008 => 0x08 0x00)

bytearrayを使ったバイト単位の出力

bytearrayを使うことでも、バイト単位でファイルに書き込むことができます。

data = bytearray([0x12, 0x34, 0xac, 0xff])

f = open('tmp.bin', 'wb')
f.write(data)
f.close()
上記を実行すると、 tmp.binというファイルが作成され、その中身は16進数で、以下のように 12 34 AC FF になります。
$ hexdump -v -C tmp.bin
00000000  12 34 ac ff                                       |.4..|
00000004

bytearray.append

bytearrayは、appendメソッドでデータの追加も可能です。

data = range(15, 15+16*6, 16)   #=> 0x0F(15)  0x1F(15+16)  ... 0x5F(15+16*5)
bt = bytearray([0, 1, 2])

f = open('tmp.bin', 'wb')
for d in data:
    bt.append(d)

f.write(bt)
f.close()
上記の実行で作成されるファイルの中身は 00 01 02 のあとに、0F 1F 2F 3F 4F 5F が続きます。
$ hexdump -v -C a.bin
00000000  00 01 02 0f 1f 2f 3f 4f  5f                       |...../?O_|
00000009

指定位置のデータだけ上書き

seekでファイルオブジェクトの位置を変更できます。

seek(offset, from_what)
第二引数のfrom_whatは参照点になり、0がファイルの先頭、1が現在のファイル位置、2がファイルの終端になります。デフォルトは0で、ファイルの先頭になります。 第一引数のoffsetは、参照点からの位置になり、from_what = 0の場合、offset = 0がファイルの先頭1バイト目の位置になります。

Python3の場合、以下のようになります。

import sys

with open('tmp.bin', 'wb') as f:
    f.seek(3)
    f.write(0x80.to_bytes(1, byteorder=sys.byteorder))
上記では、ファイルの先頭を1バイト目とすると、4バイト目を0x80に書き換えます。
 hexdump -C tmp.bin 
00000000  08 09 0a 80 0c ff                                 |......|
00000006