structとは
structモジュールは、Pythonオブジェクトとバイナリデータ(bytes)を相互変換する。
ファイルフォーマットの解析やネットワークプロトコルの処理で使うことが多い。
import struct
基本的な使い方
パック(Python → bytes)
packed = struct.pack('i', 42) # int
print(packed) # b'*\x00\x00\x00'
アンパック(bytes → Python)
unpacked = struct.unpack('i', packed)
print(unpacked) # (42,) ← タプルで返る
複数の値
# パック
packed = struct.pack('iif', 10, 20, 3.14)
# アンパック
a, b, c = struct.unpack('iif', packed)
print(a, b, c) # 10 20 3.14...
フォーマット文字
| フォーマット | C型 | サイズ |
|---|---|---|
b |
signed char | 1 |
B |
unsigned char | 1 |
h |
short | 2 |
H |
unsigned short | 2 |
i |
int | 4 |
I |
unsigned int | 4 |
q |
long long | 8 |
Q |
unsigned long long | 8 |
f |
float | 4 |
d |
double | 8 |
s |
char[] | 指定サイズ |
x |
パディング | 1 |
バイトオーダー
value = 0x12345678
# リトルエンディアン(Intel等)
struct.pack('<I', value) # b'xV4\x12'
# ビッグエンディアン
struct.pack('>I', value) # b'\x124Vx'
# ネットワーク(= ビッグエンディアン)
struct.pack('!I', value) # b'\x124Vx'
| プレフィックス | バイトオーダー |
|---|---|
< |
リトルエンディアン |
> |
ビッグエンディアン |
! |
ネットワーク |
= |
ネイティブ |
@ |
ネイティブ(アラインメント付き) |
文字列
# 固定長文字列
packed = struct.pack('5s', b'Hello')
# パディング付き
packed = struct.pack('10s', b'Hi') # 残りは\x00
# アンパック
name, = struct.unpack('10s', packed)
name = name.rstrip(b'\x00').decode()
構造体の表現
C言語の構造体:
struct Point {
int x;
int y;
};
Pythonで表現:
point = struct.pack('ii', 100, 200)
x, y = struct.unpack('ii', point)
複雑な構造体
struct Record {
char name[10];
int age;
float score;
};
record = struct.pack('10sif', b'Alice', 25, 95.5)
name, age, score = struct.unpack('10sif', record)
name = name.rstrip(b'\x00').decode()
Structクラス
繰り返し処理には事前コンパイルが効率的:
# フォーマットをコンパイル
point_struct = struct.Struct('ii')
# 複数のポイントを処理
points = [(10, 20), (30, 40), (50, 60)]
packed = b''.join(point_struct.pack(x, y) for x, y in points)
# アンパック
for data in struct.iter_unpack('ii', packed):
print(data)
実践例
BMPファイルヘッダ
BMP_HEADER = struct.Struct('<2sIHHI')
# ヘッダを解析
with open('image.bmp', 'rb') as f:
header = f.read(BMP_HEADER.size)
magic, size, _, _, offset = BMP_HEADER.unpack(header)
print(f"マジック: {magic}, サイズ: {size}")
ネットワークパケット
def create_packet(msg_type: int, data: bytes) -> bytes:
# | Type (1 byte) | Length (2 bytes) | Data |
header = struct.pack('!BH', msg_type, len(data))
return header + data
def parse_packet(packet: bytes) -> tuple:
msg_type, length = struct.unpack('!BH', packet[:3])
data = packet[3:3 + length]
return msg_type, data
# 使用
packet = create_packet(1, b"Hello!")
msg_type, data = parse_packet(packet)
PNGシグネチャ
PNG_SIGNATURE = b'\x89PNG\r\n\x1a\n'
with open('image.png', 'rb') as f:
signature = f.read(8)
if signature == PNG_SIGNATURE:
print("Valid PNG file")
calcsize
フォーマットのサイズを取得:
struct.calcsize('i') # 4
struct.calcsize('iif') # 12
struct.calcsize('10s') # 10
struct.calcsize('!IH') # 6
注意点
1. アラインメント
# ネイティブはパディングが入る
struct.calcsize('@biq') # 16(パディング含む)
# リトルエンディアンはパディングなし
struct.calcsize('<biq') # 13
2. 文字列はバイト列
# ❌
struct.pack('5s', "Hello") # TypeError
# ✓
struct.pack('5s', b"Hello")
3. 戻り値はタプル
# 単一の値でもタプル
result = struct.unpack('i', data) # (42,)
value = result[0] # または
value, = struct.unpack('i', data)
まとめ
| 用途 | 関数 |
|---|---|
| パック | struct.pack(fmt, ...) |
| アンパック | struct.unpack(fmt, bytes) |
| サイズ取得 | struct.calcsize(fmt) |
| 繰り返しアンパック | struct.iter_unpack(fmt, bytes) |
| 事前コンパイル | struct.Struct(fmt) |
バイナリデータを扱うときはstructモジュールを使いましょう!