Pythonでバイナリデータを扱う¶
バイナリデータを扱う型¶
Python標準データ型¶
Pythonの標準データ型には、バイナリデータを扱うための bytes型およびbytearray型があります。
bytesはイミュータブル、bytearrayはミュータブルです。
bytes型のデータ生成¶
- ファイル、ソケット等からバイナリデータを読み込む関数は、bytes型のデータを返却します。
- bytes のクラスメソッド fromhex で文字列リテラルからバイナリデータを生成します。
bytes.fromhex('cafe babe')
バイナリと組込みデータ型との変換¶
バイナリを整数に変換¶
data = bytes.fromhex('01 02 03 04 05 06 07 08')
int_value1 = int.from_bytes(data[:4], byteorder='little')
int_value2 = int.from_bytes(data[:4], byteorder='big')
bytes型の data の先頭から4バイトを整数に変換します。バイトの並びをビッグエンディアンで解釈するかリトルエンディアンで解釈するかを第2引数で指定します。
バイナリを文字列に変換¶
bytes型データのdecodeメソッドを呼ぶと文字列データが得られます。デフォルトのエンコーディングはUTF8なので、エンコーディングが違うときは明示的に指定します。
text = data.decode()
C言語文字列(null終端)¶
C言語などの文字列はバイナリに格納する際、文字列の最後にnull文字(0x00)を置きます。
Pythonでbytesデータをdecodeすると、0x00はnull文字として文字列に含まれてしまいます。
data.decode().rstrip('¥x00')
バイナリを構造化データに変換¶
通常バイナリデータは、複数の異なる型のデータの組み合わせで構成されます。
structモジュールは、バイナリデータを構成している型の組み合わせを事前に定義し、まとめて取り出すことに使用します。
unpack¶
import struct
data = bytes.fromhex('41 42 00 00 00 03 40 10 00 00 00 00 00 00')
unpacked = struct.unpack(">ccid", data)
print(unpacked)
バイナリデータには、キャラクタ2文字、4バイト整数(int)、8バイト浮動小数点数(double)がビッグエンディアンで詰められています。
structモジュールのunpack関数は、第1引数でバイナリデータの構造を定義し、第2引数でバイナリデータを指定すると、構造に従ってデータを解釈してタプルで返却します。
バイナリ構造の定義はちょっと特殊ですが、およそ次の通りです。
- 先頭にバイナリデータのエンディアン、パディング有無を指定。@=<>! のいずれか1文字を指定、または省略時は@が指定されたものとして扱う
は、実行環境に適するアライメントが行われる。以外はアライメントはなく詰められる
- データ型の構造(並び順)を指定。一例は c:char(1バイト)、i:int(4バイト符号付整数)、d:double(8バイト浮動小数点数)
- パディング(読み飛ばしたいバイト)はx
- 文字列は、長さを指定したs (例、5s は5バイトのバイト列)で取り出し後、decodeで文字列化
unpack_from¶
バイナリデータ(bytes変数)の読み出し位置をoffset引数で指定可能です。
連続するデータの取り出し¶
バイナリデータ(bytes変数)に同じ数値型のデータが並んでいるときに読みだしてarrayに詰めます。
import array
:
float_array = array.array('f')
float_array.frombytes(data)
バイナリの比較¶
bytes型のデータをリテラルと比較¶
contents = file.read()
magic_word = contents[:4]
if magic_word == b'\xca\xfe\xba\xbe':
print('Java class file')
ビット操作¶
ビットマスク¶
ビット演算を使う¶
整数型のデータに対して2進数リテラルを指定してビット積を取ります。
- 整数の先頭3bitをマスクし、3bitの値を取る例
ver = (data & 0b1110_0000) >> 5
bytes型の場合は、整数に変換してからビット演算をします。整数変換時は、bytes型の並びがビッグエンディアンかリトルエンディアンかを指定します。
data = int.from_bytes(bytes, "big")