The struct module converts between Python values and C-style binary data. Essential for binary protocols, file formats, and network communication.

Basic Pack/Unpack

import struct
 
# Pack values into bytes
data = struct.pack('ihf', 1, 2, 3.14)
print(data)  # b'\x01\x00\x00\x00\x02\x00\x9a\x99I@'
 
# Unpack bytes to values
values = struct.unpack('ihf', data)
print(values)  # (1, 2, 3.140000104904175)

Format Characters

CharC TypePythonSize
bsigned charint1
Bunsigned charint1
hshortint2
Hunsigned shortint2
iintint4
Iunsigned intint4
qlong longint8
Qunsigned long longint8
ffloatfloat4
ddoublefloat8
schar[]bytesn
?_Boolbool1

Byte Order

import struct
 
value = 0x12345678
 
# Native byte order (default)
struct.pack('I', value)
 
# Little-endian
struct.pack('<I', value)  # b'xV4\x12'
 
# Big-endian (network order)
struct.pack('>I', value)  # b'\x124Vx'
 
# Network order (big-endian)
struct.pack('!I', value)  # Same as >

Struct Class (Performance)

import struct
 
# Compile format once
header_format = struct.Struct('>BBHI')
 
# Reuse for multiple operations
header = header_format.pack(1, 2, 3, 4)
values = header_format.unpack(header)
 
print(header_format.size)  # 8 bytes

Packing Strings

import struct
 
# Fixed-length string
name = b'Alice'
packed = struct.pack('10s', name)  # Padded with nulls
print(packed)  # b'Alice\x00\x00\x00\x00\x00'
 
# Unpack
unpacked = struct.unpack('10s', packed)
print(unpacked[0].rstrip(b'\x00'))  # b'Alice'

Arrays of Values

import struct
 
# Pack multiple integers
numbers = [1, 2, 3, 4, 5]
packed = struct.pack(f'{len(numbers)}i', *numbers)
 
# Unpack
unpacked = struct.unpack(f'{len(numbers)}i', packed)
print(unpacked)  # (1, 2, 3, 4, 5)

Reading Binary Files

import struct
 
# BMP file header
with open('image.bmp', 'rb') as f:
    # Read 14-byte BMP header
    header = f.read(14)
    signature, size, _, _, offset = struct.unpack('<2sIHHI', header)
    
    print(f"Signature: {signature}")
    print(f"File size: {size}")
    print(f"Data offset: {offset}")

Writing Binary Files

import struct
 
def write_wav_header(f, channels, sample_rate, bits_per_sample, data_size):
    byte_rate = sample_rate * channels * bits_per_sample // 8
    block_align = channels * bits_per_sample // 8
    
    # RIFF header
    f.write(struct.pack('<4sI4s', b'RIFF', 36 + data_size, b'WAVE'))
    
    # fmt chunk
    f.write(struct.pack('<4sIHHIIHH', 
        b'fmt ', 16, 1, channels, sample_rate, 
        byte_rate, block_align, bits_per_sample))
    
    # data chunk header
    f.write(struct.pack('<4sI', b'data', data_size))

Network Protocols

import struct
import socket
 
def create_icmp_packet(icmp_type, code, checksum, identifier, sequence, data):
    """Create ICMP packet."""
    return struct.pack(
        '>BBHHH',
        icmp_type,
        code,
        checksum,
        identifier,
        sequence
    ) + data
 
def parse_ip_header(packet):
    """Parse IPv4 header."""
    header = struct.unpack('>BBHHHBBHII', packet[:20])
    return {
        'version': header[0] >> 4,
        'ihl': header[0] & 0xF,
        'ttl': header[5],
        'protocol': header[6],
        'src': socket.inet_ntoa(struct.pack('>I', header[8])),
        'dst': socket.inet_ntoa(struct.pack('>I', header[9])),
    }

unpack_from / pack_into

import struct
 
# Unpack from offset
data = b'\x00\x00\x01\x00\x02\x00\x03\x00'
values = struct.unpack_from('>HHH', data, offset=2)
print(values)  # (1, 2, 3)
 
# Pack into buffer
buffer = bytearray(10)
struct.pack_into('>HH', buffer, 2, 100, 200)
print(buffer)  # bytearray(b'\x00\x00\x00d\x00\xc8\x00\x00\x00\x00')

calcsize

import struct
 
# Get packed size
print(struct.calcsize('i'))     # 4
print(struct.calcsize('10s'))   # 10
print(struct.calcsize('>BBHI')) # 8

iter_unpack

import struct
 
# Unpack repeated structures
data = struct.pack('>HH' * 3, 1, 2, 3, 4, 5, 6)
 
for x, y in struct.iter_unpack('>HH', data):
    print(f"({x}, {y})")
# (1, 2)
# (3, 4)
# (5, 6)

Practical: Binary Record

import struct
from dataclasses import dataclass
 
@dataclass
class Record:
    id: int
    timestamp: int
    value: float
    name: bytes
    
    FORMAT = '>IQd20s'
    SIZE = struct.calcsize(FORMAT)
    
    def pack(self):
        name = self.name.ljust(20, b'\x00')[:20]
        return struct.pack(self.FORMAT, self.id, self.timestamp, self.value, name)
    
    @classmethod
    def unpack(cls, data):
        id, ts, val, name = struct.unpack(cls.FORMAT, data)
        return cls(id, ts, val, name.rstrip(b'\x00'))
 
# Usage
record = Record(1, 1711042200, 3.14, b'test')
packed = record.pack()
restored = Record.unpack(packed)

Bit Fields

import struct
 
def pack_flags(flag1, flag2, flag3, value):
    """Pack flags into single byte."""
    flags = (flag1 << 7) | (flag2 << 6) | (flag3 << 5) | (value & 0x1F)
    return struct.pack('B', flags)
 
def unpack_flags(data):
    flags = struct.unpack('B', data)[0]
    return {
        'flag1': bool(flags & 0x80),
        'flag2': bool(flags & 0x40),
        'flag3': bool(flags & 0x20),
        'value': flags & 0x1F,
    }

Padding and Alignment

import struct
 
# Native alignment includes padding
struct.calcsize('bI')   # May be 8 (3 bytes padding)
struct.calcsize('=bI')  # 5 (no padding, native byte order)
 
# Explicit padding
struct.pack('b3xI', 1, 2)  # 1 byte + 3 padding + 4 byte int

Summary

struct module essentials:

  • pack/unpack: Convert values ↔ bytes
  • Byte order: < little, > big, ! network
  • Struct class: Compile format for reuse
  • iter_unpack: Process repeated structures
  • pack_into/unpack_from: Work with buffers

Critical for binary protocols, file formats, and network programming.

React to this post: