Working with Binary Data
Pythonimport struct # Pack integers into binary data (big-endian, 2 ints) packed = struct.pack('>2i', 1, 2) # b'\x00\x00\x00\x01\x00\x00\x00\x02' # Unpack binary data back to Python values values = struct.unpack('>2i', packed) # (1, 2) # Serialize any Python object with pickle import pickle data = pickle.dumps({'key': 'value', 'nums': [1, 2, 3]}) obj = pickle.loads(data)
Choose the right module based on the task:
Progress:
- Identify data source/target: fixed-format binary (C structs, network protocols) vs. arbitrary Python objects
- Choose module:
structfor binary layouts,picklefor object serialization,shelvefor persistent dict-like storage,marshalonly for internal .pyc use (never for untrusted/external data) - For custom classes with
pickle, implement__reduce__or register withcopyregif default pickling fails - Define format strings (
struct) or protocol version (pickle) - Handle byte order, size, and alignment explicitly for
struct - Never unpickle data from untrusted sources — treat like
eval()
struct — Binary layouts
Format strings define byte order + field types:
| Char | Byte order | Size/align |
|---|---|---|
@ | native | native, aligned (default) |
= | native | standard, no align |
< | little-endian | standard, no align |
> | big-endian | standard, no align |
! | network (big-endian) | standard, no align |
Common format codes: b/B (signed/unsigned char), h/H (short), i/I (int), l/L (long), q/Q (long long), f/d (float/double), s (char array), ? (bool).
Pythonstruct.pack('<3sB', b'abc', 255) # bytes with prefix count struct.calcsize('>4i') # size in bytes without packing struct.unpack_from(buffer, offset=0) # unpack from a slice of a larger buffer
Use struct.Struct(fmt) when reusing the same format repeatedly — precompiles it for speed.
pickle — Object serialization
Pythonwith open('data.pkl', 'wb') as f: pickle.dump(obj, f, protocol=pickle.HIGHEST_PROTOCOL) with open('data.pkl', 'rb') as f: obj = pickle.load(f)
- Use
pickle.HIGHEST_PROTOCOLunless cross-version compatibility is required. - Not all objects are picklable by default (open files, sockets, lambdas, some C extension objects). Implement
__getstate__/__setstate__or__reduce__on the class.
copyreg — Extending pickle for classes you don't control
Pythonimport copyreg def pickle_point(p): return (Point, (p.x, p.y)) copyreg.pickle(Point, pickle_point)
shelve — Persistent dict-backed storage
Pythonimport shelve with shelve.open('mydata') as db: db['key'] = {'complex': 'object'} value = db['key']
Values are pickled automatically; keys must be strings.
marshal — Internal use only
Reserved for Python's own .pyc bytecode serialization. Not stable across versions, not safe for untrusted input. Do not use for application-level serialization — use pickle instead.
Example 1: Parsing a binary file header Input: A binary file starts with a 4-byte magic number, 2-byte version (uint16), 4-byte length (uint32), all big-endian. Output:
Pythonimport struct HEADER_FMT = '>4sHI' HEADER_SIZE = struct.calcsize(HEADER_FMT) # 10 bytes with open('file.bin', 'rb') as f: magic, version, length = struct.unpack(HEADER_FMT, f.read(HEADER_SIZE))
Example 2: Caching computed results to disk across runs Input: An expensive function whose results should persist between program executions, keyed by input string. Output:
Pythonimport shelve def get_cached(key, compute_fn): with shelve.open('cache.db') as db: if key not in db: db[key] = compute_fn(key) return db[key]
Example 3: Deep-copying a complex nested object graph Input: An object with circular references and custom classes that needs a full deep copy. Output:
Pythonimport pickle def deep_copy(obj): return pickle.loads(pickle.dumps(obj))
- Prefer explicit byte order (
<,>,!) over@/native to ensure portability across machines. - Use
struct.Structobjects for repeated pack/unpack in hot loops. - Always open files in binary mode (
'rb'/'wb') when working withstructorpickle. - For network protocols, use
!(network byte order = big-endian) by convention. - Version your pickle protocol explicitly in long-lived storage formats to control forward/backward compatibility.
- Use
shelvefor simple persistent key-value needs; usesqlite3or a database for anything requiring queries or concurrent access.
- Never
pickle.load()untrusted or unauthenticated data — arbitrary code execution risk, equivalent toeval(). - Forgetting struct alignment/padding differences between
@(native, padded) and</>/=(no padding) causes size mismatches. - Mixing up format string repeat counts, e.g.
'3s'(one 3-byte string) vs'sss'(three 1-byte strings). - Using
marshalfor application data persistence — it's undocumented/unstable across Python versions. - Forgetting
shelverequires closing (or usingwith) to flush writes to disk. - Assuming all Python objects are picklable by default — sockets, file handles, generators, and some lambdas are not.