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 / B | char | 整数 | 1 | 0 ~ 255 |
h / H | short | 整数 | 2 | 0 ~ 65,535 |
i / I | int | 整数 | 4 | 0 ~ 4,294,967,295 |
l / L | long | 整数 | 4 | (同上) |
q / Q | long long | 整数 | 8 | 0 ~ 1.84 x 10^19 |
f | float | 浮動小数点 | 4 | IEEE 754 単精度 |
d | double | 浮動小数点 | 8 | IEEE 754 倍精度 |
文字列・その他
| 書式 | 内容 | 用途 |
s | char[] | 固定長文字列(例:10s で10バイト) |
p | Pascal文字列 | 最初の1バイトが長さを示す特殊な文字列 |
? | _Bool | 真偽値(1バイト) |
4. 【私の所感】bytearray.extend が非常に有効な理由
バイナリパケットを組み立てる際、一気に struct.pack で作ろうとすると、フォーマット文字列が !I10sHf... と呪文のようになり、可読性が下がります。
そこで私が推奨したいのが、bytearray への extend 追加方式です。
メリット
- サイズ合わせが視覚的:
pack('10x')などで「ここは10バイト空ける」という意図が明確になる。 - 動的な構築: ループ内でデータ量が変わる場合でも、
extendなら柔軟に対応できる。 - 効率的:
+演算子での結合は毎回新しいオブジェクトを作りますが、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でのバイナリ操作はもう怖くありません。この記事が、同じ悩みを持つエンジニアの助けになれば幸いです。

コメント