struct.error: unpack requires a buffer of N bytes (tested)
struct.error: unpack requires a buffer of 8 bytes (or 4, or 16) means your buffer's length isn't exactly what the format needs. We measured the four ways that happens on Python 3.13.5, including one where your fields add up to 6 and struct wants 8.
The rule
struct.unpack(fmt, buf) needs len(buf) == struct.calcsize(fmt) exactly. Not at least: exactly. The message is the same whether you passed too few bytes or too many, so print len(buf) and struct.calcsize(fmt) first.
1. Native alignment adds padding bytes
import struct
data = b"\x01\x00\x02\x00\x00\x00" # a uint16 and a uint32, 6 bytes
for fmt in ["HI", "<HI", "=HI"]:
print(fmt, "calcsize =", struct.calcsize(fmt), end=" -> ")
try:
print(struct.unpack(fmt, data))
except struct.error as e:
print("struct.error:", e)
Output:
HI calcsize = 8 -> struct.error: unpack requires a buffer of 8 bytes
<HI calcsize = 6 -> (1, 2)
=HI calcsize = 6 -> (1, 2)
Measured on Python 3.13.5 (x86-64 Linux): without a prefix, "HI" is 8 bytes, not 6: native mode aligns the 4-byte I to a 4-byte boundary, inserting 2 pad bytes. <, >, = or ! turn alignment off. For file formats and network data, always use one of those (and pick the byte order the format specifies). More in native format padding breaks unpack.
2. The last read is short
import struct, io
f = io.BytesIO(b"\x01\x00\x00\x00" * 2 + b"\x01\x00") # 2.5 records of 4 bytes
while chunk := f.read(4):
try:
print(struct.unpack("<I", chunk))
except struct.error as e:
print("struct.error:", e, "| got", len(chunk), "bytes")
Output:
(1,)
(1,)
struct.error: unpack requires a buffer of 4 bytes | got 2 bytes
Measured: a file that isn't a whole number of records fails on the last chunk. f.read(4) returns up to 4 bytes, so check len(chunk) and decide whether a partial record means a truncated file (error) or a trailing remainder (skip). Sockets are worse: recv(4) can return fewer bytes even mid-stream, so loop until you have them.
3. Too many bytes gives the same error
import struct
data = b"\x01\x00\x00\x00\x02\x00\x00\x00extra"
try:
struct.unpack("<I", data)
except struct.error as e:
print("unpack:", e)
print("unpack_from:", struct.unpack_from("<I", data))
print("iter_unpack:", list(struct.iter_unpack("<I", data[:8])))
Output:
unpack: unpack requires a buffer of 4 bytes
unpack_from: (1,)
iter_unpack: [(1,), (2,)]
Measured: 13 bytes for a 4-byte format raises unpack requires a buffer of 4 bytes. To read a header from a larger buffer use unpack_from() (it ignores the rest; see unpack_from silently ignores trailing bytes). For a run of records, iter_unpack(), which needs a multiple of the size.
4. The bytes went through text mode
import struct
with open("/tmp/st/rec.bin", "w") as f: # text mode by mistake
f.write("\x01\x00\x00\x80")
with open("/tmp/st/rec.bin", "rb") as f:
raw = f.read()
print(len(raw), raw)
try:
print(struct.unpack("<I", raw))
except struct.error as e:
print("struct.error:", e)
Output:
5 b'\x01\x00\x00\xc2\x80'
struct.error: unpack requires a buffer of 4 bytes
Measured: 4 “bytes” written with text-mode open(..., "w") came back as 5: \x80 was UTF-8 encoded as c2 80. Write binary data with "wb" and bytes, never str.
Frequently asked questions
What does struct.error: unpack requires a buffer of N bytes mean?
The buffer length doesn't equal struct.calcsize(fmt). We measured the same message for both too few and too many bytes.
Why is struct.calcsize larger than my fields add up to?
Without a byte-order prefix, struct uses native alignment. We measured 'HI' as 8 bytes and '<HI' as 6. Use <, >, = or ! for file and network formats.
How do I unpack only the start of a larger buffer?
struct.unpack_from(fmt, buf, offset). We measured unpack failing on 13 bytes and unpack_from returning the first field.
Why did my binary file come back with extra bytes?
It was written in text mode, so characters above 0x7F were UTF-8 encoded. We measured 4 intended bytes reading back as 5.
Written with AI assistance. Measured on 2026-10-09 with Python 3.13.5 on x86-64 Linux: native vs prefixed formats, a file with a partial last record, too-long buffers with unpack, unpack_from and iter_unpack, and binary data written in text mode. Every block was run verbatim and its output pasted from that run. PlainHeaders is our extension; everything else here is built-in Python.