【決定版】Python struct.pack 攻略:エンディアンとサイズ合わせ、bytearray活用の極意

この記事は約4分で読めます。

Pythonで低レイヤの通信プロトコルやバイナリファイルを扱う際、「1バイトのズレ」や「データの逆転」に泣かされたことはありませんか?

今回は、バイナリ操作の要である struct.pack の使い方と、個人的に「最強の組み合わせ」だと確信している bytearray.extend を使った効率的なデータ構築術を、備忘録としてまとめます。

1. そもそも「エンディアン」とは何か?

バイナリデータを扱う上で避けて通れないのがエンディアン(Endianness)です。これは、2バイト以上の数値データをメモリ上に並べる際の「順序」を指します。

  • リトルエンディアン (<): 下位バイトから順に並べる。現代の多くのPC(Intel/AMD製CPU)で採用されています。
  • ビッグエンディアン (>): 上位バイトから順に並べる。人間が数字を書く順番と同じです。
  • ネットワークバイトオーダ (!): ネットワーク通信の標準。中身はビッグエンディアンと同じです。

例:16進数 0x12345678 を送る場合

  • リトルエンディアン:78 56 34 12
  • ビッグエンディアン:12 34 56 78

この指定を間違えると、データを受け取った側で数値がめちゃくちゃになるため、必ず明示的に指定する必要があります。

2. バイト順序・サイズ・アライメントの指定子

struct.pack の最初の文字で、エンディアンとアライメント(境界調整)を指定します。

記号バイト順序サイズアライメント用途
@ネイティブネイティブネイティブ実行中のPCに合わせる(デフォルト)
=ネイティブ標準なし境界調整を無効にしたい時
<リトル標準なしWindows/Intel環境用バイナリ
>ビッグ標準なし古いファイル形式や特殊なハード用
!ネットワーク標準なしTCP/IP等の通信プロトコル

3. フォーマット文字(型)一覧表:Unsignedを網羅

unsigned(符号なし)は大文字signed(符号あり)は小文字と覚えるのがコツです。

整数・浮動小数点

書式Cの型Pythonの型標準サイズ(byte)範囲(Unsigned/大文字)
xパディング(値なし)1サイズ合わせ用の「空」
b / Bchar整数10 ~ 255
h / Hshort整数20 ~ 65,535
i / Iint整数40 ~ 4,294,967,295
l / Llong整数4(同上)
q / Qlong long整数80 ~ 1.84 x 10^19
ffloat浮動小数点4IEEE 754 単精度
ddouble浮動小数点8IEEE 754 倍精度

文字列・その他

書式内容用途
schar[]固定長文字列(例:10s で10バイト)
pPascal文字列最初の1バイトが長さを示す特殊な文字列
?_Bool真偽値(1バイト)

4. 【私の所感】bytearray.extend が非常に有効な理由

バイナリパケットを組み立てる際、一気に struct.pack で作ろうとすると、フォーマット文字列が !I10sHf... と呪文のようになり、可読性が下がります。

そこで私が推奨したいのが、bytearray への extend 追加方式です。

メリット

  1. サイズ合わせが視覚的: pack('10x') などで「ここは10バイト空ける」という意図が明確になる。
  2. 動的な構築: ループ内でデータ量が変わる場合でも、extend なら柔軟に対応できる。
  3. 効率的: + 演算子での結合は毎回新しいオブジェクトを作りますが、bytearray はメモリを再利用するため高速です。

実践的な実装例

import struct

def build_packet(user_id, message):
    # 1. バッファを生成
    packet = bytearray()
    
    # 2. ヘッダー (ネットワーク順, ID: 4byte, 符号なし)
    packet.extend(struct.pack('!I', user_id))
    
    # 3. サイズ合わせ(パディング)
    # ヘッダーとボディの間に予約領域として8バイトの空きを作る
    packet.extend(struct.pack('8x'))
    
    # 4. 文字列データの追加 (UTF-8エンコードして20バイト固定)
    msg_bytes = message.encode('utf-8')[:20]
    packet.extend(struct.pack('20s', msg_bytes))
    
    return packet

# 実行
data = build_packet(1234, "Hello Python")
print(f"Size: {len(data)} bytes")
print(f"Hex: {data.hex(' ')}")

まとめ:バイナリ操作をマスターするために

  • エンディアン (<, !) は必ず明示する。
  • 符号なし (Unsigned) は大文字 (B, H, I, Q) を使う。
  • bytearray + extend + struct.pack('x') で、綺麗で効率的なコードを書く。

このパターンを身につけておけば、Pythonでのバイナリ操作はもう怖くありません。この記事が、同じ悩みを持つエンジニアの助けになれば幸いです。

コメント

タイトルとURLをコピーしました